Wstęp

Wykorzystane biblioteki

  • skimr - Umożliwia szybkie i przejrzyste generowanie statystyk opisowych dla zbiorów danych, dostarczając podsumowania dostosowane do różnych typów zmiennych.
  • corrplot - Służy do wizualizacji macierzy korelacji.
  • GGally - Rozszerza możliwości ggplot2, umożliwiając tworzenie zaawansowanych wizualizacji, takich jak macierze par wykresów czy korelogramy, co jest przydatne w analizie wielowymiarowej.
  • dplyr - Zapewnia zestaw funkcji do manipulacji danymi w sposób deklaratywny i czytelny, umożliwiając filtrowanie, sortowanie, grupowanie oraz agregowanie danych w ramach złożonych operacji.
  • plotly - Pozwala na tworzenie interaktywnych wykresów.
  • caret - Ułatwia proces budowy modeli predykcyjnych, oferując narzędzia do przygotowania danych, selekcji cech, tuningu hiperparametrów oraz oceny modeli w spójny i zintegrowany sposób.

Próbka zbioru danych o materiałach wykorzystywanych w bateriach.

Rozmiar zbioru i podsumowanie

Zbiór danych ma 17 atrybutów i 4351 rekordów.

Suma brakujących wartości w zbiorze wynosi .

Atrybuty

  • Battery ID: Identyfikator baterii.
  • Battery Formula: Wzór chemiczny materiału baterii.
  • Working Ion: Główny jon, który odpowiada za transport ładunku w baterii.
  • Formula Charge: Wzór chemiczny materiału baterii w stanie naładowanym.
  • Formula Discharge: Wzór chemiczny materiału baterii w stanie rozładowanym.
  • Max Delta Volume: Zmiana objętości w % dla danego kroku napięcia za pomocą wzoru: max(charge, discharge)/min(charge, discharge) -1.
  • Average Voltage: Średnie napięcie dla poszczególnego kroku napięcia.
  • Gravimetric Capacity: Pojemność grawimetryczna, czyli ilość energii na jednostkę masy (mAh/g).
  • Volumetric Capacity: Pojemność wolumetryczna, czyli ilość energii na jednostkę objętości (mAh/cm³).
  • Gravimetric Energy: Gęstość energii w odniesieniu do masy baterii (Wh/kg).
  • Volumetric Energy: Gęstość energii w odniesieniu do objętości baterii (Wh/L).
  • Atomic Fraction Charge: Udział atomowy składników w stanie naładowanym.
  • Atomic Fraction Discharge: Udział atomowy składników w stanie rozładowanym.
  • Stability Charge: Wskaźnik stabilności materiału w stanie naładowanym.
  • Stability Discharge: Wskaźnik stabilności materiału w stanie rozładowanym.
  • Steps: Liczba odrębnych kroków napięcia od pełnego naładowania do rozładowania, oparta na stabilnych stanach pośrednich.
  • Max Voltage Step: Maksymalna bezwzględna różnica między sąsiednimi krokami napięcia.

Krótkie podsumowanie zbioru

Zbiór zawiera 12 atrybutów numerycznych i 5 znakowych w tym 3 kategoryczne.

Typy kolumn: - 5 kolumny znakowe, - 12 kolumny numeryczne, - 0 kolumny logiczne (jeśli istnieją).

numerical_attrs <- colnames(select(data, (Max.Delta.Volume : Max.Voltage.Step)))
data %>%
  select(Working.Ion, Max.Delta.Volume:Stability.Discharge) %>%
  group_by(Working.Ion) %>%
  summarise(across(everything(), mean))
NA

Analiza atrybutów kategorycznych

Rozkład główneych jonów

Na przedstawionym wykresie zaprezentowano rozkład różnych głównych jonów używanych do transportu ładunku w bateriach. Wyraźnie dominuje lit (Li), który występuje znacznie częściej niż inne jony. Sugeruje to powszechne zastosowanie technologii opartych na litowo-jonowych rozwiązaniach, co jest zgodne z ich szerokim wykorzystaniem w przemyśle elektroniki i magazynowania energii. Pozostałe jony, takie jak wapń (Ca), magnez (Mg), i cynk (Zn), również znajdują zastosowanie, ale w znacznie mniejszym zakresie. Obecność jonów takich jak sód (Na) i potas (K) wskazuje na badania nad alternatywami dla litu, jednak ich zastosowanie jest obecnie ograniczone.

Wzory chemiczne materiału baterii

W zbiorze jest 2096 różnych wzorów chemicznych materiałów baterii. Najczęściej występującymi wzorami są MnO2, TiO2, Vo2, CrO2, NiO2, FeO2.


continous_data <- select(data, (Max.Delta.Volume:Stability.Discharge))

for (attr_name in names(continous_data)) {
  p <- ggplot(data, aes_string(x = attr_name)) +
    geom_density(fill = "skyblue", alpha = 0.5) +
    labs(title = paste("Density Plot of", attr_name),
         x = "Max Delta Volume",
         y = "Density") +
    theme_minimal()
  print(p)
}               
Ostrzeżenie: `aes_string()` was deprecated in ggplot2 3.0.0.
Please use tidy evaluation idioms with `aes()`. 
See also `vignette("ggplot2-in-packages")` for more information.

NA
NA

Korelacja zmiennych

Najwyższy wspolczynnik koorelacji wystepuje pomiedzy parami atrybutów:
- Gravimetric Energy i Volumetric Energy
- Gravimetric Capcity i Volumetric Capacity
- Stability Charge i Stability Discharge
- Gravimetric Capacity i Atomic Fraction Discharge
- Average Voltage i Gravimetric Energy

Wykres korelacji pomiędzy energią grawimetryczną i wolumetryczną

correlation <- cor(data$Gravimetric.Energy, data$Volumetric.Energy, use = "complete.obs")

ggplot(data, aes(x = Gravimetric.Energy, y = Volumetric.Energy)) +
  geom_point(color = "darkorange", size = 3, alpha = 0.7) +
  geom_smooth(method = "lm", color = "blue", se = FALSE, linetype = "dashed") +
  labs(title = "Energy Density Relationship",
       subtitle = paste("Correlation =", round(correlation, 2)),
       x = "Gravimetric Energy Density (Wh/kg)",
       y = "Volumetric Energy Density (Wh/L)") +
  theme_light()

Wykres przedstawia zależność między gęstością energii wolumetryczną (Wh/L, energia na jednostkę objętości) a grawimetryczną (Wh/kg, energia na jednostkę masy), gdzie widoczna jest silna korelacja dodatnia między tymi parametrami. Gęstość energii jest kluczowym wskaźnikiem wydajności baterii - im wyższa wartość, tym więcej energii może być zmagazynowane w danej objętości lub masie baterii, co jest szczególnie istotne w zastosowaniach mobilnych, takich jak pojazdy elektryczne czy urządzenia przenośne. Większość badanych materiałów skupia się w zakresie do 2000 Wh/kg i 7500 Wh/L, choć występuje kilka obiecujących wyjątków o wyższych parametrach, które mogą stanowić potencjalne kierunki rozwoju nowych, wydajniejszych baterii.

Wykres korelacji pomiędzy pojemnością grawimetryczną i wolumetryczną

correlation <- cor(data$Gravimetric.Capacity, data$Volumetric.Capacity, use = "complete.obs")

ggplot(data, aes(x = Gravimetric.Capacity, y = Volumetric.Capacity)) +
  geom_point(color = "darkorange", size = 3, alpha = 0.7) +
  geom_smooth(method = "lm", color = "blue", se = FALSE) +
  labs(title = "Relationship Between Gravimetric and Volumetric Capacity",
       subtitle = paste("Correlation =", round(correlation, 2)),
       x = "Gravimetric Capacity (mAh/g)",
       y = "Volumetric Capacity (mAh/cm³)") +
  theme_light()

Wykres przedstawia zależność między pojemnością wolumetryczną (mAh/cm³, ilość ładunku na jednostkę objętości) a grawimetryczną (mAh/g, ilość ładunku na jednostkę masy) materiałów, gdzie pojemność grawimetryczna określa ile energii można zmagazynować w danej masie materiału, a wolumetryczna - ile w danej objętości, co ma kluczowe znaczenie przy projektowaniu baterii o różnym przeznaczeniu. Współczynnik korelacji 0.86 wskazuje na silną zależność między tymi parametrami, choć nie tak silną jak w przypadku gęstości energii. Na przykład, materiał o wysokiej pojemności grawimetrycznej może być lekki, ale zajmować dużo miejsca, podczas gdy materiał o wysokiej pojemności wolumetrycznej może być cięższy, ale bardziej kompaktowy - wybór między nimi zależy od konkretnego zastosowania baterii.

Wykres korelacji pomiędzy stabilnością materiału w stanie naładowanym i rozładowanym

correlation <- cor(data$Stability.Charge, data$Stability.Discharge, use = "complete.obs")

ggplot(data, aes(x = Stability.Charge, y = Stability.Discharge)) +
  geom_point(color = "darkgreen", size = 3, alpha = 0.7) +
  geom_smooth(method = "lm", color = "blue", se = FALSE, linetype = "dashed") +
  labs(title = "Stability Charge vs Stability Discharge",
       subtitle = paste("Correlation =", round(correlation, 2)),
       x = "Stability Charge",
       y = "Stability Discharge") +
  theme_minimal()

Wykres przedstawia zależność między stabilnością materiału w stanie naładowanym (Stability Charge) a stabilnością w stanie rozładowanym (Stability Discharge), ze współczynnikiem korelacji 0.8 wskazującym na silną dodatnią zależność. Stabilność materiału jest kluczowym parametrem określającym, jak dobrze materiał zachowuje swoją strukturę i właściwości podczas cykli ładowania i rozładowania - im niższa wartość, tym materiał jest bardziej stabilny i bezpieczny w użytkowaniu. Większość badanych materiałów skupia się w zakresie niskich wartości (0-2) dla obu parametrów, co jest pożądane, natomiast punkty odstające o wyższych wartościach (powyżej 4) mogą wskazywać na materiały problematyczne, które mogą być mniej odpowiednie do zastosowań w bateriach ze względu na potencjalną niestabilność.

Wykres korelacji pomiędzy pojemnością grawimetryczną i udział atomowym składników w stanie rozładowanym

correlation <- cor(data$Gravimetric.Capacity, data$Atomic.Fraction.Discharge, use = "complete.obs")

ggplot(data, aes(x = Gravimetric.Capacity, y = Atomic.Fraction.Discharge)) +
  geom_point(aes(color = Atomic.Fraction.Discharge), size = 3, alpha = 0.7) +
  geom_smooth(method = "loess", color = "red", se = TRUE, fill = "gray80") +
  labs(title = "Gravimetric Capacity vs Atomic Fraction Discharge",
       subtitle = paste("Correlation =", round(correlation, 2)),
       x = "Gravimetric Capacity (mAh/g)",
       y = "Atomic Fraction Discharge") +
  scale_color_gradient(low = "orange", high = "purple") +
  theme_light()

Wykres przedstawia zależność między pojemnością grawimetryczną (Gravimetric Capacity, mAh/g) a udziałem atomowym w stanie rozładowania (Atomic Fraction Discharge). Można zaobserwować umiarkowanie silną, dodatnią zależność, co potwierdza współczynnik korelacji wynoszący 0.68. W miarę wzrostu pojemności grawimetrycznej, udział atomowy w stanie rozładowania zwiększa się, osiągając wartość maksymalną bliską 1.0.

Kolor punktów reprezentuje wartość Atomic Fraction Discharge, gdzie jaśniejsze kolory wskazują na niższe wartości, a ciemniejsze na wyższe. Dane wskazują, że większość obserwacji znajduje się w zakresie niskiej pojemności grawimetrycznej (<1000 mAh/g), a dla wartości powyżej 2000 mAh/g zależność staje się nieliniowa.

Sugeruje to, że materiały o wyższej pojemności grawimetrycznej mają tendencję do osiągania wyższych udziałów atomowych w stanie rozładowania.

unique(data$Steps)
[1] 1 2 3 4 5 6
filteredData <- data %>%filter(data$Steps == 3)
correlation <- cor(filteredData$Average.Voltage, filteredData$Gravimetric.Energy, use = "complete.obs")

ggplot(filteredData, aes(x = Average.Voltage, y = Gravimetric.Energy)) +
  geom_point(color = "darkblue", size = 3, alpha = 0.7) +
  geom_smooth(method = "lm", color = "darkred", se = FALSE, linetype = "dashed") +
  labs(title = "Average Voltage vs Gravimetric Energy",
       subtitle = paste("Correlation =", round(correlation, 2)),
       x = "Average Voltage (V)",
       y = "Gravimetric Energy Density (Wh/kg)") +
  theme_classic()

# Generowanie wykresów dla każdej wartości Steps
p <- ggplot(data, aes(x = Average.Voltage, y = Gravimetric.Energy)) +
  geom_point(color = "darkblue", size = 3, alpha = 0.7) +
  geom_smooth(method = "lm", color = "darkred", se = FALSE, linetype = "dashed") +
  facet_wrap(~ Steps, scales = "free") +
  labs(title = "Average Voltage vs Gravimetric Energy for Each Step",
       x = "Average Voltage (V)",
       y = "Gravimetric Energy Density (Wh/kg)") +
  theme_classic()
ggplotly(p)
`geom_smooth()` using formula = 'y ~ x'
library(ggplot2)
library(dplyr)
library(plotly)

# Oblicz korelacje dla każdej grupy
data_with_cor <- data %>%
  group_by(Steps) %>%
  summarise(
    correlation = cor(Average.Voltage, Gravimetric.Energy, use = "complete.obs"),
    n_cases = n()  # Dodaj liczbę przypadków
  )

# Stwórz wykres
p <- ggplot(data, aes(x = Average.Voltage, y = Gravimetric.Energy)) +
  geom_point(color = "darkblue", size = 3, alpha = 0.7) +
  geom_smooth(method = "lm", color = "darkred", se = FALSE, linetype = "dashed") +
  facet_wrap(~ Steps, scales = "free", 
             labeller = labeller(Steps = function(x) {
               # Znajdź korelację dla danego etykiety
                row <- data_with_cor[data_with_cor$Steps == x, ]
               # Sformatuj etykietę z korelacją
              paste0(x, " (Corr = ", round(row$correlation, 2), 
                      ", n = ", row$n_cases, ")")
             })) +
  labs(
    title = "Average Voltage vs Gravimetric Energy for Each Step",
    x = "Average Voltage (V)",
    y = "Gravimetric Energy Density (Wh/kg)"
  ) +
  theme_classic()

# Konwersja do wykresu interaktywnego
ggplotly(p)
`geom_smooth()` using formula = 'y ~ x'

Wykresy przedstawiają zależności między średnim napięciem (Average Voltage, V) a gęstością energii grawimetrycznej (Gravimetric Energy Density, Wh/kg) dla różnych wartości kroku napięcia. Dane wskazują, że wraz ze wzrostem średniego napięcia zwiększa się gęstość energii grawimetrycznej. Większość danych skupia się w zakresie niskich wartości napięcia (<10 V), co sugeruje, że materiały o wyższym napięciu są mniej liczne, ale wykazują większą efektywność energetyczną.

Wizualizacja interaktywnych wykresów pudełkowych

W tej części raportu przedstawiono interaktywne wykresy pudełkowe dla każdej zmiennej numerycznej w zbiorze danych. Wykresy te umożliwiają eksplorację rozkładu wartości, identyfikację potencjalnych wartości odstających oraz porównanie zmienności w każdej zmiennej.

for (attr_name in names(number_attr_data)) {
  p <- plot_ly(
    number_attr_data, 
    y = ~get(attr_name),
    type = "box",
    boxpoints = "all",
    jitter = 0.3,
    pointpos = -1.8
  ) %>%
    layout(
      title = paste("Boxplot of", attr_name),
      yaxis = list(title = attr_name)
    )

  print(p)
}
NA
data %>%
  group_by(Battery.Formula) %>%
  summarise(total = n()) %>%
  arrange(desc(total))
data %>%
  group_by(Battery.Formula) %>%
  summarise(total = n()) %>%
  arrange(desc(total))

Analiza właściwości

Stabilność w stanie naładowanym i rozładowanym

Stability Charge

  • Określa stabilność materiału w baterii, gdy jest w pełni naładowany
  • Wyższa stabilność oznacza, że materiał będzie mniej podatny na uszkodzenia lub degradację podczas ładowania. Jest to istotne, aby zapewnić długotrwałe działanie baterii bez utraty jej właściwości.

Stability Discharge

  • Określa stabilność materiału w baterii, gdy jest w pełni rozładowany.
  • Stabilność w stanie rozładowanym jest kluczowa dla utrzymania efektywności baterii przez wiele cykli ładowania i rozładowania. Zapewnia ona, że materiał nie ulegnie degradacji, co mogłoby prowadzić do zmniejszenia pojemności i żywotności baterii.
summary_stability_charge_working_ion <- data %>%
  group_by(Working.Ion) %>%
  summarise(stability_charge_mean = mean(Stability.Charge),
            stability_charge_median = median(Stability.Charge),
            stability_charge_min = min(Stability.Charge),
            stability_charge_max = max(Stability.Charge),
            total = n()) %>%
  arrange(desc(stability_charge_median))

summary_stability_charge_working_ion

ggplot(summary_stability_charge_working_ion, aes(x = total, y = stability_charge_median)) +
  geom_point(color = "blue", size = 3) +
  labs(
    title = "Relation between Total and Stability Charge Mean",
    x = "Total",
    y = "Stability Charge Mean"
  ) +
  theme_minimal()


plot_ly(summary_stability_charge_working_ion, x = ~total, y = ~stability_charge_median, type = 'scatter', mode = 'markers', text = ~paste(Working.Ion))

summary_stability_discharge_working_ion <- data %>%
  group_by(Working.Ion) %>%
  summarise(stability_discharge_mean = mean(Stability.Discharge),
            stability_discharge_median = median(Stability.Discharge),
            stability_discharge_min = min(Stability.Discharge),
            stability_discharge_max = max(Stability.Discharge),
            total = n()) %>%
  arrange(desc(stability_discharge_median))

summary_stability_discharge_working_ion

ggplot(summary_stability_discharge_working_ion, aes(x = total, y = stability_discharge_median, label = Working.Ion)) +
  geom_point(color = "blue", size = 3) +
  geom_label() +
  labs(
    title = "Relation between Total and Stability Discharge Mean",
    x = "Total",
    y = "Stability Discharge Mean"
  ) +
  theme_minimal()


plot_ly(summary_stability_discharge_working_ion, x = ~total, y = ~stability_discharge_median, type = 'scatter', mode = 'markers', text = ~paste(Working.Ion))

Materiały, które wykazują najwyższą stabilność w stanie naładowanm to Y, Al, ZN, Ca, Mg, a w stanie rozładowanym Y, Mg, Zn, Al.

summary_max_data_volume_working_ion <- data %>%
  group_by(Working.Ion) %>%
  summarise(max_data_volume_mean = mean(Max.Delta.Volume),
            max_data_volume_median = median(Max.Delta.Volume),
            max_data_volume_min = min(Max.Delta.Volume),
            max_data_volume_max = max(Max.Delta.Volume),
            total = n()) %>%
  arrange(max_data_volume_mean)

summary_max_data_volume_working_ion

plot_ly(summary_max_data_volume_working_ion, x = ~total, y = ~max_data_volume_median, type = 'scatter', mode = 'markers', text = ~paste(Working.Ion))

Pojemność i gęstość grawimetryczna

Pojemność Grawimetryczna

  • Mierzy, ile energii elektrycznej bateria może przechować w przeliczeniu na jednostkę masy (mAh/g).
  • Im wyższa pojemność grawimetryczna, tym więcej energii bateria może przechowywać przy tej samej masie. Jest to istotne dla urządzeń przenośnych, gdzie zależy nam na maksymalizacji energii przy niewielkiej masie.

Gęstość Grawimetryczna

  • Oznacza, ile energii dostępnej jest w baterii w odniesieniu do jej masy (Wh/kg).
  • Gęstość grawimetryczna informuje, jak efektywnie bateria magazynuje energię w stosunku do swojej wagi. Jest kluczowa dla aplikacji, gdzie ważna jest zarówno pojemność energetyczna, jak i lekkość, np. w samochodach elektrycznych czy dronach.

Największą pojemnością grawimetryczną charaketeryzują się materiały Al, Y, Mg, Ca, Zn, a gęstością grawimetryczną Ca, Li, Y, Na, Mg.



formula_charge_summary <- data %>%
  group_by(Formula.Charge) %>%
  summarise(total = n()) %>%
  arrange(desc(total))

percentile_99 <- quantile(select(formula_charge_summary, total), probs = 0.99, na.rm = TRUE)
formula_charge_values_99_percentile <- formula_charge_summary %>%
  filter(total >= percentile_99)

formula_charge_values_99_percentile


summary_data <- data %>%
  filter(Formula.Charge %in% formula_charge_values_99_percentile$Formula.Charge) %>%
  group_by(Formula.Charge) %>%
  summarise(total = n(),
    stability_charge_mean = mean(Stability.Charge),
            stability_charge_median = median(Stability.Charge),
            stability_charge_min = min(Stability.Charge),
            stability_charge_max = max(Stability.Charge)) %>%
  arrange(desc(total))


ggplot(summary_data, aes(x = total, y = stability_charge_mean)) +
  geom_point(color = "blue", size = 3) +
  geom_smooth(method = "lm", se = FALSE, color = "red") +  # Optional trend line
  labs(
    title = "Relation between Total and Stability Charge Mean",
    x = "Total",
    y = "Stability Charge Mean"
  ) +
  theme_minimal()

# Perform ANOVA
#anova_result <- aov(Max.Delta.Volume ~ Battery.Formula, data = data)
#summary(anova_result)

anova_result <- aov(Volumetric.Energy ~ Battery.Formula, data = data)
summary(anova_result)
                  Df    Sum Sq Mean Sq F value Pr(>F)    
Battery.Formula 3300 6.619e+09 2005785   2.977 <2e-16 ***
Residuals       1050 7.075e+08  673841                   
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Predykcja

Jako zmienną celu obrano pojemnośc grawimetryczną.

//TODO delete

library("caret")
set.seed(23)

dataset_to_train  <- select(data, -(Battery.ID:Formula.Discharge))

if (is.factor(data$Stability.Charge)) {
    # Optional: If it should be numeric in the first place
    data$Stability.Charge <- as.numeric(as.character(data$Stability.Charge))
}


head(dataset_to_train)

inTraining <- 
    createDataPartition(
        y = data$Stability.Charge,
        p = .75,
        list = FALSE)

training <- dataset_to_train[ inTraining,]
testing  <- dataset_to_train[-inTraining,]

ctrl <- trainControl(
    # powtórzona ocena krzyżowa
    method = "repeatedcv",
    # liczba podziałów
    number = 2,
    # liczba powtórzeń
    repeats = 5)

fit <- train(Stability.Charge ~ .,
             data = training,
             method = "rf",
             trControl = ctrl,
             # Paramter dla algorytmu uczącego
             ntree = 10)

fit
Random Forest 

3264 samples
  11 predictor

No pre-processing
Resampling: Cross-Validated (2 fold, repeated 5 times) 
Summary of sample sizes: 1633, 1631, 1632, 1632, 1632, 1632, ... 
Resampling results across tuning parameters:

  mtry  RMSE       Rsquared   MAE       
   2    0.2446747  0.6656663  0.08814636
   6    0.1820564  0.8036072  0.06937326
  11    0.1834491  0.7999853  0.06828544

RMSE was used to select the optimal model using the smallest value.
The final value used for the model was mtry = 6.
idx <- createDataPartition(data$Gravimetric.Capacity,p=0.7, list=F)
d1 <- data.frame(gravimetricCapacity=data[idx,]$Gravimetric.Capacity)
d2 <- data.frame(gravimetricCapacity=data[-idx,]$Gravimetric.Capacity)

ggplot(mapping=aes(alpha=0.4)) + 
 geom_density(aes(gravimetricCapacity, fill="red"), d1) + 
 geom_density(aes(gravimetricCapacity, fill="blue"), d2) + 
 theme_minimal()

Przygotowanie zbioru do trenowania

Ze zbioru usunięto atrybuty:
- ID - nie ma wpływu na zmienną celu - Battery.Formula - bardzo dużo unikalnych wartości, więcej niż połowa rozmiaru zbioru, istnieje ryzyko przeuczenia i nadmiernej segmentacji - Formula.Charge - podobnie jak Battery.Formula - bardzo dużo unikalnych wartości - Formula.Discharge - podobnie jak wyżej

Skalowanie i normalizacja zmiennych

#dataset_train_selected_attr  <- select(data, -c(Battery.ID, Battery.Formula, Formula.Charge, Formula.Discharge))
dataset_numerical_attrs <- select(data, numerical_attrs)
Ostrzeżenie: Using an external vector in selections was deprecated in tidyselect 1.1.0.
Please use `all_of()` or `any_of()` instead.
# Was:
data %>% select(numerical_attrs)

# Now:
data %>% select(all_of(numerical_attrs))

See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
preProc <- preProcess(dataset_numerical_attrs, method = c("center", "scale"))
data_scaled <- predict(preProc, newdata = dataset_numerical_attrs)
data_scaled$Working.Ion <- data$Working.Ion
data_scaled
skim(data_scaled)
── Data Summary ────────────────────────
                           Values     
Name                       data_scaled
Number of rows             4351       
Number of columns          13         
_______________________               
Column type frequency:                
  character                1          
  numeric                  12         
________________________              
Group variables            None       
dummies <- dummyVars("~ .", data = data_scaled)
data_transformed <- data.frame(predict(dummies, newdata = data_scaled))

head(data_transformed)
anova_result <- aov(Gravimetric.Capacity ~ Battery.Formula, data = data)
summary(anova_result)
                  Df    Sum Sq Mean Sq   F value Pr(>F)    
Battery.Formula 3300 118304814   35850 1.103e+26 <2e-16 ***
Residuals       1050         0       0                     
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Usunięcie zmiennych skorelowanych

# Usunięcie zmiennych skorelowanych
cor_matrix <- cor(data_transformed)
high_corr <- findCorrelation(cor_matrix, cutoff = 0.9)
data_reduced <- data_transformed[, -high_corr]
colnames(data_reduced[high_corr])
[1] "Volumetric.Energy"

targetAttr <- "Volumetric.Energy"
target <- data_reduced$Volumetric.Energy
predictors <- data_reduced[, -which(names(data_reduced) == targetAttr)]
data_reduced
# Podział danych
set.seed(123)
trainIndex <- createDataPartition(target, p = 0.7, list = FALSE)
trainData <- data_reduced[trainIndex, ]
testData <- data_reduced[-trainIndex, ]
ggplot() +
  geom_density(data = trainData, aes(x = !!sym(targetAttr), fill = "Train"), alpha = 0.3) +
  geom_density(data = testData, aes(x = !!sym(targetAttr), fill = "Test"), alpha = 0.3) +
  scale_fill_manual(values = c("Train" = "red", "Test" = "blue")) +
  theme_minimal() +
  labs(title = "Porównanie rozkładu zmiennej celu w zbiorach treningowym i testowym",
       x = targetAttr,
       y = "Gęstość",
       fill = "Zbiór")

targetAttr <- "Volumetric.Energy"
formula <- as.formula(paste(targetAttr, "~ ."))

model <- train(
  formula, 
  data = trainData, 
  method = "lm", 
  trControl = trainControl(method = "cv", number = 10)
)
# Prognozy i ocena modelu
predictions <- predict(model, newdata = testData)
postResample(predictions, testData[[targetAttr]])
     RMSE  Rsquared       MAE 
0.6327532 0.6168179 0.4215428 
# Wykres rzeczywistych vs przewidywanych wartości
plot(testData[[targetAttr]], predictions, 
     main = paste("Predicted vs Actual", targetAttr),
     xlab = "Rzeczywiste", ylab = "Przewidywane")
abline(0, 1, col = "red")

LS0tDQp0aXRsZTogIlByb2dyYW1vd2FuaWUgdyBSOiBQcm9qZWt0Ig0Kc3VidGl0bGU6ICAgIkFuYWxpemEgYmF6eSBkYW55Y2ggbWF0ZXJpYcWCw7N3IHd5a29yenlzdHl3YW55Y2ggdyB0d29yemVuaXUgYmF0ZXJpaSINCmF1dGhvcjogS2xhdWRpYSBLb3dhbHNrYQ0KZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJWQgJUIgJVknKWAiDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KZWRpdG9yX29wdGlvbnM6IA0KICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lDQotLS0NCg0KDQojIFdzdMSZcA0KDQpgYGB7ciBnbG9iYWwgb3B0aW9ucywgaW5jbHVkZSA9IEZBTFNFfQ0KI2tuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvPUZBTFNFLCBpbmNsdWRlID0gRkFMU0UsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UpDQpzZXQuc2VlZCgxMikNCmBgYA0KDQojIyBXeWtvcnp5c3RhbmUgYmlibGlvdGVraQ0KDQotICoqc2tpbXIqKiAtIFVtb8W8bGl3aWEgc3p5YmtpZSBpIHByemVqcnp5c3RlIGdlbmVyb3dhbmllIHN0YXR5c3R5ayBvcGlzb3d5Y2ggZGxhIHpiaW9yw7N3IGRhbnljaCwgZG9zdGFyY3phasSFYyBwb2RzdW1vd2FuaWEgZG9zdG9zb3dhbmUgZG8gcsOzxbxueWNoIHR5cMOzdyB6bWllbm55Y2guIA0KLSAqKmNvcnJwbG90KiogLSBTxYJ1xbx5IGRvIHdpenVhbGl6YWNqaSBtYWNpZXJ6eSBrb3JlbGFjamkuIA0KLSAqKkdHYWxseSoqIC0gUm96c3plcnphIG1vxbxsaXdvxZtjaSBnZ3Bsb3QyLCB1bW/FvGxpd2lhasSFYyB0d29yemVuaWUgemFhd2Fuc293YW55Y2ggd2l6dWFsaXphY2ppLCB0YWtpY2ggamFrIG1hY2llcnplIHBhciB3eWtyZXPDs3cgY3p5IGtvcmVsb2dyYW15LCBjbyBqZXN0IHByenlkYXRuZSB3IGFuYWxpemllIHdpZWxvd3ltaWFyb3dlai4gDQotICoqZHBseXIqKiAtIFphcGV3bmlhIHplc3RhdyBmdW5rY2ppIGRvIG1hbmlwdWxhY2ppIGRhbnltaSB3IHNwb3PDs2IgZGVrbGFyYXR5d255IGkgY3p5dGVsbnksIHVtb8W8bGl3aWFqxIVjIGZpbHRyb3dhbmllLCBzb3J0b3dhbmllLCBncnVwb3dhbmllIG9yYXogYWdyZWdvd2FuaWUgZGFueWNoIHcgcmFtYWNoIHrFgm/FvG9ueWNoIG9wZXJhY2ppLiANCi0gKipwbG90bHkqKiAtIFBvendhbGEgbmEgdHdvcnplbmllIGludGVyYWt0eXdueWNoIHd5a3Jlc8Ozdy4gDQotICoqY2FyZXQqKiAtIFXFgmF0d2lhIHByb2NlcyBidWRvd3kgbW9kZWxpIHByZWR5a2N5am55Y2gsIG9mZXJ1asSFYyBuYXJ6xJlkemlhIGRvIHByenlnb3Rvd2FuaWEgZGFueWNoLCBzZWxla2NqaSBjZWNoLCB0dW5pbmd1IGhpcGVycGFyYW1ldHLDs3cgb3JheiBvY2VueSBtb2RlbGkgdyBzcMOzam55IGkgemludGVncm93YW55IHNwb3PDs2IuDQoNCg0KYGBge3IgZWNobz1GQUxTRSwgcmVzdWx0cz0naGlkZSd9DQpsaWJyYXJ5KHNraW1yKQ0KbGlicmFyeShjb3JycGxvdCkNCmxpYnJhcnkoR0dhbGx5KQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeSgiSG1pc2MiKQ0KbGlicmFyeShtbGJlbmNoKQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkocmxhbmcpDQpsaWJyYXJ5KGtuaXRyKQ0KbGlicmFyeShEVCkNCmxpYnJhcnkoZHBseXIpDQpgYGANCg0KUHLDs2JrYSB6YmlvcnUgZGFueWNoIG8gbWF0ZXJpYcWCYWNoIHd5a29yenlzdHl3YW55Y2ggdyBiYXRlcmlhY2guDQoNCmBgYHtyIGxvYWQgZGF0YSwgZWNobyA9IEZBTFNFLCBjYWNoZSA9IFRSVUUsIHJlc3VsdHM9RkFMU0V9DQojdXJsZmlsZT0iaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL0tsYXVkaWFLL0JhdHRlcnktbWF0ZXJpYWxzLWFuYWx5c2lzL3JlZnMvaGVhZHMvbWFzdGVyL21wX2JhdHRlcmllcy5jc3YiDQojZGF0YSA8LSByZWFkLmNzdih1cmwodXJsZmlsZSkpDQoNCmRhdGEgPC0gcmVhZC5jc3YoIkQ6XFxzdHVkaWFcXG1hZ2lzdGVya2FcXFpFRFxcUHJvamVrdF9SXFxtcF9iYXR0ZXJpZXMuY3N2IikNCmBgYA0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCmhlYWQoZGF0YSkNCmBgYA0KDQojIyBSb3ptaWFyIHpiaW9ydSBpIHBvZHN1bW93YW5pZSANCg0KWmJpw7NyIGRhbnljaCBtYSBgciBuY29sKGRhdGEpYCBhdHJ5YnV0w7N3IGkgYHIgbnJvdyhkYXRhKWAgcmVrb3Jkw7N3Lg0KDQpgYGB7ciBjb3VudF9uYV92YWx1ZXMsIGluY2x1ZGU9RkFMU0V9DQpzdW1fbmEgPC0gc3VtKGlzLm5hKGRhdGEpKQ0KYGBgDQoNClN1bWEgYnJha3VqxIVjeWNoIHdhcnRvxZtjaSB3IHpiaW9yemUgd3lub3NpIC4NCg0KIyMgQXRyeWJ1dHkNCg0KLSAqKkJhdHRlcnkgSUQqKjogSWRlbnR5ZmlrYXRvciBiYXRlcmlpLg0KLSAqKkJhdHRlcnkgRm9ybXVsYSoqOiBXesOzciBjaGVtaWN6bnkgbWF0ZXJpYcWCdSBiYXRlcmlpLg0KLSAqKldvcmtpbmcgSW9uKio6IEfFgsOzd255IGpvbiwga3TDs3J5IG9kcG93aWFkYSB6YSB0cmFuc3BvcnQgxYJhZHVua3UgdyBiYXRlcmlpLg0KLSAqKkZvcm11bGEgQ2hhcmdlKio6IFd6w7NyIGNoZW1pY3pueSBtYXRlcmlhxYJ1IGJhdGVyaWkgdyBzdGFuaWUgbmHFgmFkb3dhbnltLg0KLSAqKkZvcm11bGEgRGlzY2hhcmdlKio6IFd6w7NyIGNoZW1pY3pueSBtYXRlcmlhxYJ1IGJhdGVyaWkgdyBzdGFuaWUgcm96xYJhZG93YW55bS4NCi0gKipNYXggRGVsdGEgVm9sdW1lKio6IFptaWFuYSBvYmrEmXRvxZtjaSB3ICUgZGxhIGRhbmVnbyBrcm9rdSBuYXBpxJljaWEgemEgcG9tb2PEhSB3em9ydTogbWF4KGNoYXJnZSwgZGlzY2hhcmdlKS9taW4oY2hhcmdlLCBkaXNjaGFyZ2UpIC0xLg0KLSAqKkF2ZXJhZ2UgVm9sdGFnZSoqOiDFmnJlZG5pZSBuYXBpxJljaWUgZGxhIHBvc3pjemVnw7NsbmVnbyBrcm9rdSBuYXBpxJljaWEuDQotICoqR3JhdmltZXRyaWMgQ2FwYWNpdHkqKjogUG9qZW1ub8WbxIcgZ3Jhd2ltZXRyeWN6bmEsIGN6eWxpIGlsb8WbxIcgZW5lcmdpaSBuYSBqZWRub3N0a8SZIG1hc3kgKG1BaC9nKS4NCi0gKipWb2x1bWV0cmljIENhcGFjaXR5Kio6IFBvamVtbm/Fm8SHIHdvbHVtZXRyeWN6bmEsIGN6eWxpIGlsb8WbxIcgZW5lcmdpaSBuYSBqZWRub3N0a8SZIG9iasSZdG/Fm2NpIChtQWgvY23CsykuDQotICoqR3JhdmltZXRyaWMgRW5lcmd5Kio6IEfEmXN0b8WbxIcgZW5lcmdpaSB3IG9kbmllc2llbml1IGRvIG1hc3kgYmF0ZXJpaSAoV2gva2cpLg0KLSAqKlZvbHVtZXRyaWMgRW5lcmd5Kio6IEfEmXN0b8WbxIcgZW5lcmdpaSB3IG9kbmllc2llbml1IGRvIG9iasSZdG/Fm2NpIGJhdGVyaWkgKFdoL0wpLg0KLSAqKkF0b21pYyBGcmFjdGlvbiBDaGFyZ2UqKjogVWR6aWHFgiBhdG9tb3d5IHNrxYJhZG5pa8OzdyB3IHN0YW5pZSBuYcWCYWRvd2FueW0uDQotICoqQXRvbWljIEZyYWN0aW9uIERpc2NoYXJnZSoqOiBVZHppYcWCIGF0b21vd3kgc2vFgmFkbmlrw7N3IHcgc3RhbmllIHJvesWCYWRvd2FueW0uDQotICoqU3RhYmlsaXR5IENoYXJnZSoqOiBXc2thxbpuaWsgc3RhYmlsbm/Fm2NpIG1hdGVyaWHFgnUgdyBzdGFuaWUgbmHFgmFkb3dhbnltLg0KLSAqKlN0YWJpbGl0eSBEaXNjaGFyZ2UqKjogV3NrYcW6bmlrIHN0YWJpbG5vxZtjaSBtYXRlcmlhxYJ1IHcgc3RhbmllIHJvesWCYWRvd2FueW0uDQotICoqU3RlcHMqKjogTGljemJhIG9kcsSZYm55Y2gga3Jva8OzdyBuYXBpxJljaWEgb2QgcGXFgm5lZ28gbmHFgmFkb3dhbmlhIGRvIHJvesWCYWRvd2FuaWEsIG9wYXJ0YSBuYSBzdGFiaWxueWNoIHN0YW5hY2ggcG/Fm3JlZG5pY2guDQotICoqTWF4IFZvbHRhZ2UgU3RlcCoqOiBNYWtzeW1hbG5hIGJlend6Z2zEmWRuYSByw7PFvG5pY2EgbWnEmWR6eSBzxIVzaWVkbmltaSBrcm9rYW1pIG5hcGnEmWNpYS4NCg0KYGBge3IgcHJ0LCBlY2hvPUZBTFNFfQ0KcHJldHR5VGFibGUgPC0gZnVuY3Rpb24odGFibGVfZGYsIHJvdW5kX2RpZ2l0cz0yKSB7DQogICAgRFQ6OmRhdGF0YWJsZSh0YWJsZV9kZiwgc3R5bGU9ImJvb3RzdHJhcCIsIGZpbHRlciA9ICJ0b3AiLCByb3duYW1lcyA9IEZBTFNFLCBleHRlbnNpb25zID0gIkJ1dHRvbnMiLCBvcHRpb25zID0gbGlzdChkb20gPSAnQmZydGlwJywgYnV0dG9ucyA9IGMoJ2NvcHknLCAnY3N2JywgJ2V4Y2VsJywgJ3BkZicsICdwcmludCcpKSkgJT4lIGZvcm1hdFJvdW5kKG5hbWVzKGRwbHlyOjpzZWxlY3RfaWYodGFibGVfZGYsIGlzLm51bWVyaWMpKSwgcm91bmRfZGlnaXRzKQ0KfQ0KYGBgDQoNCiMjIyBLcsOzdGtpZSBwb2RzdW1vd2FuaWUgemJpb3J1DQoNCmBgYHtybSBlY2hvPUZBTFNFfQ0Kc2tpbV9zdW1tYXJ5IDwtIHNraW0oZGF0YSkNCg0KbnVtX2NoYXJhY3RlciA8LSBza2ltX3N1bW1hcnkgJT4lIGZpbHRlcihza2ltX3R5cGUgPT0gImNoYXJhY3RlciIpICU+JSBucm93KCkNCm51bV9udW1lcmljIDwtIHNraW1fc3VtbWFyeSAlPiUgZmlsdGVyKHNraW1fdHlwZSA9PSAibnVtZXJpYyIpICU+JSBucm93KCkNCm51bV9sb2dpY2FsIDwtIHNraW1fc3VtbWFyeSAlPiUgZmlsdGVyKHNraW1fdHlwZSA9PSAibG9naWNhbCIpICU+JSBucm93KCkNCmBgYA0KDQpaYmnDs3IgemF3aWVyYSAxMiBhdHJ5YnV0w7N3IG51bWVyeWN6bnljaCBpIDUgem5ha293eWNoIHcgdHltIDMga2F0ZWdvcnljem5lLg0KDQpUeXB5IGtvbHVtbjoNCi0gYHIgbnVtX2NoYXJhY3RlcmAga29sdW1ueSB6bmFrb3dlLA0KLSBgciBudW1fbnVtZXJpY2Aga29sdW1ueSBudW1lcnljem5lLA0KLSBgciBudW1fbG9naWNhbGAga29sdW1ueSBsb2dpY3puZSAoamXFm2xpIGlzdG5pZWrEhSkuDQoNCmBgYHtyfQ0KbnVtZXJpY2FsX2F0dHJzIDwtIGNvbG5hbWVzKHNlbGVjdChkYXRhLCAoTWF4LkRlbHRhLlZvbHVtZSA6IE1heC5Wb2x0YWdlLlN0ZXApKSkNCmBgYA0KDQpgYGB7cn0NCmRhdGEgJT4lDQogIHNlbGVjdChXb3JraW5nLklvbiwgTWF4LkRlbHRhLlZvbHVtZTpTdGFiaWxpdHkuRGlzY2hhcmdlKSAlPiUNCiAgZ3JvdXBfYnkoV29ya2luZy5Jb24pICU+JQ0KICBzdW1tYXJpc2UoYWNyb3NzKGV2ZXJ5dGhpbmcoKSwgbWVhbikpDQoNCmBgYA0KDQojIyMgQW5hbGl6YSBhdHJ5YnV0w7N3IGthdGVnb3J5Y3pueWNoDQoNCiMjIyMgUm96a8WCYWQgZ8WCw7N3bmV5Y2ggam9uw7N3DQoNCmBgYHtyIGVjaG8gPSBGQUxTRX0NCg0KZGF0YSAlPiUNCiAgZ3JvdXBfYnkoV29ya2luZy5Jb24pICU+JQ0KICBzdW1tYXJpc2UodG90YWxfaW9uID0gbigpKSAlPiUNCiAgZ2dwbG90KCkgKw0KICAgbGFicyh0aXRsZSA9IHBhc3RlKCJSb3prxYJhZCBnxYLDs3dueWNoIGpvbsOzdywga3TDs3JlIG9kcG93aWFkYWrEhSB6YSB0cmFuc3BvcnQgxYJhZHVua3UgdyBiYXRlcmlpIiksDQogICAgICAgICAgIHggPSAiV29ya2luZy5Jb24iLA0KICAgICAgICAgICB5ID0gIkNvdW50IikgKw0KICAgICAgZ2VvbV9iYXIoYWVzKHggPSByZW9yZGVyKFdvcmtpbmcuSW9uLCB0b3RhbF9pb24pLCB5ID0gdG90YWxfaW9uLCBmaWxsID0gV29ya2luZy5Jb24pLCBzdGF0ID0gImlkZW50aXR5IikgKw0KICAgICAgdGhlbWVfbWluaW1hbCgpIA0KYGBgDQpOYSBwcnplZHN0YXdpb255bSB3eWtyZXNpZSB6YXByZXplbnRvd2FubyByb3prxYJhZCByw7PFvG55Y2ggZ8WCw7N3bnljaCBqb27Ds3cgdcW8eXdhbnljaCBkbyB0cmFuc3BvcnR1IMWCYWR1bmt1IHcgYmF0ZXJpYWNoLiBXeXJhxbpuaWUgZG9taW51amUgbGl0IChMaSksIGt0w7NyeSB3eXN0xJlwdWplIHpuYWN6bmllIGN6xJnFm2NpZWogbmnFvCBpbm5lIGpvbnkuIFN1Z2VydWplIHRvIHBvd3N6ZWNobmUgemFzdG9zb3dhbmllIHRlY2hub2xvZ2lpIG9wYXJ0eWNoIG5hIGxpdG93by1qb25vd3ljaCByb3p3acSFemFuaWFjaCwgY28gamVzdCB6Z29kbmUgeiBpY2ggc3plcm9raW0gd3lrb3J6eXN0YW5pZW0gdyBwcnplbXnFm2xlIGVsZWt0cm9uaWtpIGkgbWFnYXp5bm93YW5pYSBlbmVyZ2lpLiBQb3pvc3RhxYJlIGpvbnksIHRha2llIGphayB3YXDFhCAoQ2EpLCBtYWduZXogKE1nKSwgaSBjeW5rIChabiksIHLDs3duaWXFvCB6bmFqZHVqxIUgemFzdG9zb3dhbmllLCBhbGUgdyB6bmFjem5pZSBtbmllanN6eW0gemFrcmVzaWUuIE9iZWNub8WbxIcgam9uw7N3IHRha2ljaCBqYWsgc8OzZCAoTmEpIGkgcG90YXMgKEspIHdza2F6dWplIG5hIGJhZGFuaWEgbmFkIGFsdGVybmF0eXdhbWkgZGxhIGxpdHUsIGplZG5hayBpY2ggemFzdG9zb3dhbmllIGplc3Qgb2JlY25pZSBvZ3Jhbmljem9uZS4NCg0KDQojIyMjIFd6b3J5IGNoZW1pY3puZSBtYXRlcmlhxYJ1IGJhdGVyaWkNCg0KYGBge3IgZWNobyA9IEZBTFNFfQ0KcHJpbnQoY291bnQodW5pcXVlKGRhdGFbIkZvcm11bGEuQ2hhcmdlIl0pKSkNCg0KZm9ybXVsYV9jaGFyZ2Vfc3VtbWFyeSA8LSBkYXRhICU+JQ0KICBncm91cF9ieShGb3JtdWxhLkNoYXJnZSkgJT4lDQogIHN1bW1hcmlzZSh0b3RhbCA9IG4oKSkgJT4lDQogIGFycmFuZ2UoZGVzYyh0b3RhbCkpDQoNCnBlcmNlbnRpbGVfOTkgPC0gcXVhbnRpbGUoc2VsZWN0KGZvcm11bGFfY2hhcmdlX3N1bW1hcnksIHRvdGFsKSwgcHJvYnMgPSAwLjk5LCBuYS5ybSA9IFRSVUUpDQpmb3JtdWxhX2NoYXJnZV92YWx1ZXNfOTlfcGVyY2VudGlsZSA8LSBmb3JtdWxhX2NoYXJnZV9zdW1tYXJ5ICU+JQ0KICBmaWx0ZXIodG90YWwgPj0gcGVyY2VudGlsZV85OSkNCg0KZm9ybXVsYV9jaGFyZ2VfdmFsdWVzXzk5X3BlcmNlbnRpbGUNCg0KZm9ybXVsYV9jaGFyZ2VfdmFsdWVzXzk5X3BlcmNlbnRpbGUgJT4lDQogIGdncGxvdCgpICsNCiAgIGxhYnModGl0bGUgPSBwYXN0ZSgiV3rDs3J5IGNoZW1pY3puZSBtYXRlcmlhxYJ1IGJhdGVyaWkgdyBzdGFuaWUgbmHFgmFkb3dhbnltIiksDQogICAgICAgICAgIHggPSAiRm9ybXVsYSBDaGFyZ2UiLA0KICAgICAgICAgICB5ID0gIkNvdW50IikgKw0KICAgICAgZ2VvbV9iYXIoYWVzKHggPSByZW9yZGVyKEZvcm11bGEuQ2hhcmdlLCB0b3RhbCksIHkgPSB0b3RhbCwgZmlsbCA9IEZvcm11bGEuQ2hhcmdlKSwgc3RhdCA9ICJpZGVudGl0eSIsICB3aWR0aCA9IDAuNSkgKw0KICAgICAgdGhlbWUoDQogICAgICAgIHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxLCBzaXplID0gOCkNCiAgICAgICkNCmBgYA0KVyB6YmlvcnplIGplc3QgMjA5NiByw7PFvG55Y2ggd3pvcsOzdyBjaGVtaWN6bnljaCBtYXRlcmlhxYLDs3cgYmF0ZXJpaS4gTmFqY3rEmcWbY2llaiB3eXN0xJlwdWrEhWN5bWkgd3pvcmFtaSBzxIUgTW5PMiwgVGlPMiwgVm8yLCBDck8yLCBOaU8yLCBGZU8yLg0KDQojIyMjIA0KDQoNCmBgYHtyfQ0KDQpjb250aW5vdXNfZGF0YSA8LSBzZWxlY3QoZGF0YSwgKE1heC5EZWx0YS5Wb2x1bWU6U3RhYmlsaXR5LkRpc2NoYXJnZSkpDQoNCmZvciAoYXR0cl9uYW1lIGluIG5hbWVzKGNvbnRpbm91c19kYXRhKSkgew0KICBwIDwtIGdncGxvdChkYXRhLCBhZXNfc3RyaW5nKHggPSBhdHRyX25hbWUpKSArDQogICAgZ2VvbV9kZW5zaXR5KGZpbGwgPSAic2t5Ymx1ZSIsIGFscGhhID0gMC41KSArDQogICAgbGFicyh0aXRsZSA9IHBhc3RlKCJEZW5zaXR5IFBsb3Qgb2YiLCBhdHRyX25hbWUpLA0KICAgICAgICAgeCA9ICJNYXggRGVsdGEgVm9sdW1lIiwNCiAgICAgICAgIHkgPSAiRGVuc2l0eSIpICsNCiAgICB0aGVtZV9taW5pbWFsKCkNCiAgcHJpbnQocCkNCn0gICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICAgIA0KDQpgYGANCiMjIEtvcmVsYWNqYSB6bWllbm55Y2gNCg0KYGBge3IsIGVjaG89RkFMU0V9DQpudW1iZXJfYXR0cl9kYXRhIDwtIHNlbGVjdChkYXRhLCAoTWF4LkRlbHRhLlZvbHVtZTpNYXguVm9sdGFnZS5TdGVwKSkNCg0KY29yX2RhdGEgPC0gY29yKG51bWJlcl9hdHRyX2RhdGEpDQoNCmNvcnJwbG90Lm1peGVkKGNvcl9kYXRhLA0KICB0bC5wb3MgPSAnbHQnLA0KICBudW1iZXIuY2V4ID0gMC41LA0KICBvcmRlciA9ICdBT0UnDQopDQpgYGANCg0KTmFqd3nFvHN6eSB3c3BvbGN6eW5uaWsga29vcmVsYWNqaSB3eXN0ZXB1amUgcG9taWVkenkgcGFyYW1pIGF0cnlidXTDs3c6XA0KLSBHcmF2aW1ldHJpYyBFbmVyZ3kgaSBWb2x1bWV0cmljIEVuZXJneSBcDQotIEdyYXZpbWV0cmljIENhcGNpdHkgaSBWb2x1bWV0cmljIENhcGFjaXR5IFwNCi0gU3RhYmlsaXR5IENoYXJnZSBpIFN0YWJpbGl0eSBEaXNjaGFyZ2UgXA0KLSBHcmF2aW1ldHJpYyBDYXBhY2l0eSBpIEF0b21pYyBGcmFjdGlvbiBEaXNjaGFyZ2UgXA0KLSBBdmVyYWdlIFZvbHRhZ2UgaSBHcmF2aW1ldHJpYyBFbmVyZ3kgDQoNCiMjIyBXeWtyZXMga29yZWxhY2ppIHBvbWnEmWR6eSBlbmVyZ2nEhSBncmF3aW1ldHJ5Y3puxIUgaSB3b2x1bWV0cnljem7EhQ0KDQpgYGB7cn0NCmNvcnJlbGF0aW9uIDwtIGNvcihkYXRhJEdyYXZpbWV0cmljLkVuZXJneSwgZGF0YSRWb2x1bWV0cmljLkVuZXJneSwgdXNlID0gImNvbXBsZXRlLm9icyIpDQoNCmdncGxvdChkYXRhLCBhZXMoeCA9IEdyYXZpbWV0cmljLkVuZXJneSwgeSA9IFZvbHVtZXRyaWMuRW5lcmd5KSkgKw0KICBnZW9tX3BvaW50KGNvbG9yID0gImRhcmtvcmFuZ2UiLCBzaXplID0gMywgYWxwaGEgPSAwLjcpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgY29sb3IgPSAiYmx1ZSIsIHNlID0gRkFMU0UsIGxpbmV0eXBlID0gImRhc2hlZCIpICsNCiAgbGFicyh0aXRsZSA9ICJFbmVyZ3kgRGVuc2l0eSBSZWxhdGlvbnNoaXAiLA0KICAgICAgIHN1YnRpdGxlID0gcGFzdGUoIkNvcnJlbGF0aW9uID0iLCByb3VuZChjb3JyZWxhdGlvbiwgMikpLA0KICAgICAgIHggPSAiR3JhdmltZXRyaWMgRW5lcmd5IERlbnNpdHkgKFdoL2tnKSIsDQogICAgICAgeSA9ICJWb2x1bWV0cmljIEVuZXJneSBEZW5zaXR5IChXaC9MKSIpICsNCiAgdGhlbWVfbGlnaHQoKQ0KYGBgDQpXeWtyZXMgcHJ6ZWRzdGF3aWEgKip6YWxlxbxub8WbxIcgbWnEmWR6eSBnxJlzdG/Fm2NpxIUgZW5lcmdpaSB3b2x1bWV0cnljem7EhSoqIChXaC9MLCBlbmVyZ2lhIG5hIGplZG5vc3RrxJkgb2JqxJl0b8WbY2kpICoqYSBncmF3aW1ldHJ5Y3puxIUqKiAoV2gva2csIGVuZXJnaWEgbmEgamVkbm9zdGvEmSBtYXN5KSwgZ2R6aWUgd2lkb2N6bmEgamVzdCAqKnNpbG5hIGtvcmVsYWNqYSoqIGRvZGF0bmlhIG1pxJlkenkgdHltaSBwYXJhbWV0cmFtaS4gR8SZc3RvxZvEhyBlbmVyZ2lpIGplc3QgKiprbHVjem93eW0gd3NrYcW6bmlraWVtIHd5ZGFqbm/Fm2NpIGJhdGVyaWkqKiAtIGltIHd5xbxzemEgd2FydG/Fm8SHLCB0eW0gd2nEmWNlaiBlbmVyZ2lpIG1vxbxlIGJ5xIcgem1hZ2F6eW5vd2FuZSB3IGRhbmVqIG9iasSZdG/Fm2NpIGx1YiBtYXNpZSBiYXRlcmlpLCBjbyBqZXN0IHN6Y3plZ8OzbG5pZSBpc3RvdG5lIHcgemFzdG9zb3dhbmlhY2ggbW9iaWxueWNoLCB0YWtpY2ggamFrIHBvamF6ZHkgZWxla3RyeWN6bmUgY3p5IHVyesSFZHplbmlhIHByemVub8WbbmUuICoqV2nEmWtzem/Fm8SHIGJhZGFueWNoIG1hdGVyaWHFgsOzdyBza3VwaWEgc2nEmSB3IHpha3Jlc2llIGRvIDIwMDAgV2gva2cgaSA3NTAwIFdoL0wqKiwgY2hvxIcgd3lzdMSZcHVqZSBraWxrYSBvYmllY3VqxIVjeWNoIHd5asSFdGvDs3cgbyB3ecW8c3p5Y2ggcGFyYW1ldHJhY2gsIGt0w7NyZSBtb2fEhSBzdGFub3dpxIcgcG90ZW5jamFsbmUga2llcnVua2kgcm96d29qdSBub3d5Y2gsIHd5ZGFqbmllanN6eWNoIGJhdGVyaWkuDQoNCg0KIyMjIFd5a3JlcyBrb3JlbGFjamkgcG9tacSZZHp5IHBvamVtbm/Fm2NpxIUgZ3Jhd2ltZXRyeWN6bsSFIGkgd29sdW1ldHJ5Y3puxIUNCg0KYGBge3J9DQpjb3JyZWxhdGlvbiA8LSBjb3IoZGF0YSRHcmF2aW1ldHJpYy5DYXBhY2l0eSwgZGF0YSRWb2x1bWV0cmljLkNhcGFjaXR5LCB1c2UgPSAiY29tcGxldGUub2JzIikNCg0KZ2dwbG90KGRhdGEsIGFlcyh4ID0gR3JhdmltZXRyaWMuQ2FwYWNpdHksIHkgPSBWb2x1bWV0cmljLkNhcGFjaXR5KSkgKw0KICBnZW9tX3BvaW50KGNvbG9yID0gImRhcmtvcmFuZ2UiLCBzaXplID0gMywgYWxwaGEgPSAwLjcpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgY29sb3IgPSAiYmx1ZSIsIHNlID0gRkFMU0UpICsNCiAgbGFicyh0aXRsZSA9ICJSZWxhdGlvbnNoaXAgQmV0d2VlbiBHcmF2aW1ldHJpYyBhbmQgVm9sdW1ldHJpYyBDYXBhY2l0eSIsDQogICAgICAgc3VidGl0bGUgPSBwYXN0ZSgiQ29ycmVsYXRpb24gPSIsIHJvdW5kKGNvcnJlbGF0aW9uLCAyKSksDQogICAgICAgeCA9ICJHcmF2aW1ldHJpYyBDYXBhY2l0eSAobUFoL2cpIiwNCiAgICAgICB5ID0gIlZvbHVtZXRyaWMgQ2FwYWNpdHkgKG1BaC9jbcKzKSIpICsNCiAgdGhlbWVfbGlnaHQoKQ0KDQpgYGANCld5a3JlcyBwcnplZHN0YXdpYSAqKnphbGXFvG5vxZvEhyBtacSZZHp5IHBvamVtbm/Fm2NpxIUgd29sdW1ldHJ5Y3puxIUqKiAobUFoL2NtwrMsIGlsb8WbxIcgxYJhZHVua3UgbmEgamVkbm9zdGvEmSBvYmrEmXRvxZtjaSkgKiphIGdyYXdpbWV0cnljem7EhSoqIChtQWgvZywgaWxvxZvEhyDFgmFkdW5rdSBuYSBqZWRub3N0a8SZIG1hc3kpIG1hdGVyaWHFgsOzdywgZ2R6aWUgcG9qZW1ub8WbxIcgZ3Jhd2ltZXRyeWN6bmEgb2tyZcWbbGEgaWxlIGVuZXJnaWkgbW/FvG5hIHptYWdhenlub3dhxIcgdyBkYW5laiBtYXNpZSBtYXRlcmlhxYJ1LCBhIHdvbHVtZXRyeWN6bmEgLSBpbGUgdyBkYW5laiBvYmrEmXRvxZtjaSwgY28gbWEga2x1Y3pvd2Ugem5hY3plbmllIHByenkgcHJvamVrdG93YW5pdSBiYXRlcmlpIG8gcsOzxbxueW0gcHJ6ZXpuYWN6ZW5pdS4gKipXc3DDs8WCY3p5bm5payBrb3JlbGFjamkgMC44NiB3c2thenVqZSBuYSBzaWxuxIUgemFsZcW8bm/Fm8SHIG1pxJlkenkgdHltaSBwYXJhbWV0cmFtaSoqLCBjaG/EhyBuaWUgdGFrIHNpbG7EhSBqYWsgdyBwcnp5cGFka3UgZ8SZc3RvxZtjaSBlbmVyZ2lpLiBOYSBwcnp5a8WCYWQsIG1hdGVyaWHFgiBvIHd5c29raWVqIHBvamVtbm/Fm2NpIGdyYXdpbWV0cnljem5laiBtb8W8ZSBiecSHIGxla2tpLCBhbGUgemFqbW93YcSHIGR1xbxvIG1pZWpzY2EsIHBvZGN6YXMgZ2R5IG1hdGVyaWHFgiBvIHd5c29raWVqIHBvamVtbm/Fm2NpIHdvbHVtZXRyeWN6bmVqIG1vxbxlIGJ5xIcgY2nEmcW8c3p5LCBhbGUgYmFyZHppZWoga29tcGFrdG93eSAtIHd5YsOzciBtacSZZHp5IG5pbWkgemFsZcW8eSBvZCBrb25rcmV0bmVnbyB6YXN0b3Nvd2FuaWEgYmF0ZXJpaS4NCg0KIyMjIFd5a3JlcyBrb3JlbGFjamkgcG9tacSZZHp5IHN0YWJpbG5vxZtjacSFIG1hdGVyaWHFgnUgdyBzdGFuaWUgbmHFgmFkb3dhbnltIGkgcm96xYJhZG93YW55bQ0KDQpgYGB7cn0NCmNvcnJlbGF0aW9uIDwtIGNvcihkYXRhJFN0YWJpbGl0eS5DaGFyZ2UsIGRhdGEkU3RhYmlsaXR5LkRpc2NoYXJnZSwgdXNlID0gImNvbXBsZXRlLm9icyIpDQoNCmdncGxvdChkYXRhLCBhZXMoeCA9IFN0YWJpbGl0eS5DaGFyZ2UsIHkgPSBTdGFiaWxpdHkuRGlzY2hhcmdlKSkgKw0KICBnZW9tX3BvaW50KGNvbG9yID0gImRhcmtncmVlbiIsIHNpemUgPSAzLCBhbHBoYSA9IDAuNykgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBjb2xvciA9ICJibHVlIiwgc2UgPSBGQUxTRSwgbGluZXR5cGUgPSAiZGFzaGVkIikgKw0KICBsYWJzKHRpdGxlID0gIlN0YWJpbGl0eSBDaGFyZ2UgdnMgU3RhYmlsaXR5IERpc2NoYXJnZSIsDQogICAgICAgc3VidGl0bGUgPSBwYXN0ZSgiQ29ycmVsYXRpb24gPSIsIHJvdW5kKGNvcnJlbGF0aW9uLCAyKSksDQogICAgICAgeCA9ICJTdGFiaWxpdHkgQ2hhcmdlIiwNCiAgICAgICB5ID0gIlN0YWJpbGl0eSBEaXNjaGFyZ2UiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQpgYGANCld5a3JlcyBwcnplZHN0YXdpYSAqKnphbGXFvG5vxZvEhyBtacSZZHp5IHN0YWJpbG5vxZtjacSFIG1hdGVyaWHFgnUgdyBzdGFuaWUgbmHFgmFkb3dhbnltKiogKFN0YWJpbGl0eSBDaGFyZ2UpICoqYSBzdGFiaWxub8WbY2nEhSB3IHN0YW5pZSByb3rFgmFkb3dhbnltKiogKFN0YWJpbGl0eSBEaXNjaGFyZ2UpLCB6ZSB3c3DDs8WCY3p5bm5pa2llbSBrb3JlbGFjamkgMC44IHdza2F6dWrEhWN5bSBuYSBzaWxuxIUgZG9kYXRuacSFIHphbGXFvG5vxZvEhy4gU3RhYmlsbm/Fm8SHIG1hdGVyaWHFgnUgamVzdCAqKmtsdWN6b3d5bSBwYXJhbWV0cmVtIG9rcmXFm2xhasSFY3ltLCBqYWsgZG9icnplIG1hdGVyaWHFgiB6YWNob3d1amUgc3dvasSFIHN0cnVrdHVyxJkgaSB3xYJhxZtjaXdvxZtjaSBwb2RjemFzIGN5a2xpIMWCYWRvd2FuaWEgaSByb3rFgmFkb3dhbmlhKiogLSBpbSBuacW8c3phIHdhcnRvxZvEhywgdHltIG1hdGVyaWHFgiBqZXN0IGJhcmR6aWVqIHN0YWJpbG55IGkgYmV6cGllY3pueSB3IHXFvHl0a293YW5pdS4gV2nEmWtzem/Fm8SHIGJhZGFueWNoIG1hdGVyaWHFgsOzdyBza3VwaWEgc2nEmSB3IHpha3Jlc2llIG5pc2tpY2ggd2FydG/Fm2NpICgwLTIpIGRsYSBvYnUgcGFyYW1ldHLDs3csIGNvIGplc3QgcG/FvMSFZGFuZSwgbmF0b21pYXN0IHB1bmt0eSBvZHN0YWrEhWNlIG8gd3nFvHN6eWNoIHdhcnRvxZtjaWFjaCAocG93ecW8ZWogNCkgbW9nxIUgd3NrYXp5d2HEhyBuYSBtYXRlcmlhxYJ5IHByb2JsZW1hdHljem5lLCBrdMOzcmUgbW9nxIUgYnnEhyBtbmllaiBvZHBvd2llZG5pZSBkbyB6YXN0b3Nvd2HFhCB3IGJhdGVyaWFjaCB6ZSB3emdsxJlkdSBuYSBwb3RlbmNqYWxuxIUgbmllc3RhYmlsbm/Fm8SHLg0KDQojIyMgV3lrcmVzIGtvcmVsYWNqaSBwb21pxJlkenkgcG9qZW1ub8WbY2nEhSBncmF3aW1ldHJ5Y3puxIUgaSB1ZHppYcWCIGF0b21vd3ltIHNrxYJhZG5pa8OzdyB3IHN0YW5pZSByb3rFgmFkb3dhbnltDQoNCmBgYHtyfQ0KY29ycmVsYXRpb24gPC0gY29yKGRhdGEkR3JhdmltZXRyaWMuQ2FwYWNpdHksIGRhdGEkQXRvbWljLkZyYWN0aW9uLkRpc2NoYXJnZSwgdXNlID0gImNvbXBsZXRlLm9icyIpDQoNCmdncGxvdChkYXRhLCBhZXMoeCA9IEdyYXZpbWV0cmljLkNhcGFjaXR5LCB5ID0gQXRvbWljLkZyYWN0aW9uLkRpc2NoYXJnZSkpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBBdG9taWMuRnJhY3Rpb24uRGlzY2hhcmdlKSwgc2l6ZSA9IDMsIGFscGhhID0gMC43KSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsb2VzcyIsIGNvbG9yID0gInJlZCIsIHNlID0gVFJVRSwgZmlsbCA9ICJncmF5ODAiKSArDQogIGxhYnModGl0bGUgPSAiR3JhdmltZXRyaWMgQ2FwYWNpdHkgdnMgQXRvbWljIEZyYWN0aW9uIERpc2NoYXJnZSIsDQogICAgICAgc3VidGl0bGUgPSBwYXN0ZSgiQ29ycmVsYXRpb24gPSIsIHJvdW5kKGNvcnJlbGF0aW9uLCAyKSksDQogICAgICAgeCA9ICJHcmF2aW1ldHJpYyBDYXBhY2l0eSAobUFoL2cpIiwNCiAgICAgICB5ID0gIkF0b21pYyBGcmFjdGlvbiBEaXNjaGFyZ2UiKSArDQogIHNjYWxlX2NvbG9yX2dyYWRpZW50KGxvdyA9ICJvcmFuZ2UiLCBoaWdoID0gInB1cnBsZSIpICsNCiAgdGhlbWVfbGlnaHQoKQ0KDQpgYGANCg0KV3lrcmVzIHByemVkc3Rhd2lhIHphbGXFvG5vxZvEhyBtacSZZHp5IHBvamVtbm/Fm2NpxIUgZ3Jhd2ltZXRyeWN6bsSFIChHcmF2aW1ldHJpYyBDYXBhY2l0eSwgbUFoL2cpIGEgdWR6aWHFgmVtIGF0b21vd3ltIHcgc3RhbmllIHJvesWCYWRvd2FuaWEgKEF0b21pYyBGcmFjdGlvbiBEaXNjaGFyZ2UpLiBNb8W8bmEgemFvYnNlcndvd2HEhyB1bWlhcmtvd2FuaWUgc2lsbsSFLCBkb2RhdG5pxIUgemFsZcW8bm/Fm8SHLCBjbyBwb3R3aWVyZHphIHdzcMOzxYJjenlubmlrIGtvcmVsYWNqaSB3eW5vc3rEhWN5IDAuNjguIFcgbWlhcsSZIHd6cm9zdHUgcG9qZW1ub8WbY2kgZ3Jhd2ltZXRyeWN6bmVqLCB1ZHppYcWCIGF0b21vd3kgdyBzdGFuaWUgcm96xYJhZG93YW5pYSB6d2nEmWtzemEgc2nEmSwgb3NpxIVnYWrEhWMgd2FydG/Fm8SHIG1ha3N5bWFsbsSFIGJsaXNrxIUgMS4wLg0KDQpLb2xvciBwdW5rdMOzdyByZXByZXplbnR1amUgd2FydG/Fm8SHIEF0b21pYyBGcmFjdGlvbiBEaXNjaGFyZ2UsIGdkemllIGphxZtuaWVqc3plIGtvbG9yeSB3c2thenVqxIUgbmEgbmnFvHN6ZSB3YXJ0b8WbY2ksIGEgY2llbW5pZWpzemUgbmEgd3nFvHN6ZS4gRGFuZSB3c2thenVqxIUsIMW8ZSB3acSZa3N6b8WbxIcgb2JzZXJ3YWNqaSB6bmFqZHVqZSBzacSZIHcgemFrcmVzaWUgbmlza2llaiBwb2plbW5vxZtjaSBncmF3aW1ldHJ5Y3puZWogKDwxMDAwIG1BaC9nKSwgYSBkbGEgd2FydG/Fm2NpIHBvd3nFvGVqIDIwMDAgbUFoL2cgemFsZcW8bm/Fm8SHIHN0YWplIHNpxJkgbmllbGluaW93YS4NCg0KU3VnZXJ1amUgdG8sIMW8ZSBtYXRlcmlhxYJ5IG8gd3nFvHN6ZWogcG9qZW1ub8WbY2kgZ3Jhd2ltZXRyeWN6bmVqIG1hasSFIHRlbmRlbmNqxJkgZG8gb3NpxIVnYW5pYSB3ecW8c3p5Y2ggdWR6aWHFgsOzdyBhdG9tb3d5Y2ggdyBzdGFuaWUgcm96xYJhZG93YW5pYS4NCg0KYGBge3J9DQp1bmlxdWUoZGF0YSRTdGVwcykNCg0KZmlsdGVyZWREYXRhIDwtIGRhdGEgJT4lZmlsdGVyKGRhdGEkU3RlcHMgPT0gMykNCmNvcnJlbGF0aW9uIDwtIGNvcihmaWx0ZXJlZERhdGEkQXZlcmFnZS5Wb2x0YWdlLCBmaWx0ZXJlZERhdGEkR3JhdmltZXRyaWMuRW5lcmd5LCB1c2UgPSAiY29tcGxldGUub2JzIikNCg0KZ2dwbG90KGZpbHRlcmVkRGF0YSwgYWVzKHggPSBBdmVyYWdlLlZvbHRhZ2UsIHkgPSBHcmF2aW1ldHJpYy5FbmVyZ3kpKSArDQogIGdlb21fcG9pbnQoY29sb3IgPSAiZGFya2JsdWUiLCBzaXplID0gMywgYWxwaGEgPSAwLjcpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgY29sb3IgPSAiZGFya3JlZCIsIHNlID0gRkFMU0UsIGxpbmV0eXBlID0gImRhc2hlZCIpICsNCiAgbGFicyh0aXRsZSA9ICJBdmVyYWdlIFZvbHRhZ2UgdnMgR3JhdmltZXRyaWMgRW5lcmd5IiwNCiAgICAgICBzdWJ0aXRsZSA9IHBhc3RlKCJDb3JyZWxhdGlvbiA9Iiwgcm91bmQoY29ycmVsYXRpb24sIDIpKSwNCiAgICAgICB4ID0gIkF2ZXJhZ2UgVm9sdGFnZSAoVikiLA0KICAgICAgIHkgPSAiR3JhdmltZXRyaWMgRW5lcmd5IERlbnNpdHkgKFdoL2tnKSIpICsNCiAgdGhlbWVfY2xhc3NpYygpDQoNCmBgYA0KYGBge3J9DQojIEdlbmVyb3dhbmllIHd5a3Jlc8OzdyBkbGEga2HFvGRlaiB3YXJ0b8WbY2kgU3RlcHMNCnAgPC0gZ2dwbG90KGRhdGEsIGFlcyh4ID0gQXZlcmFnZS5Wb2x0YWdlLCB5ID0gR3JhdmltZXRyaWMuRW5lcmd5KSkgKw0KICBnZW9tX3BvaW50KGNvbG9yID0gImRhcmtibHVlIiwgc2l6ZSA9IDMsIGFscGhhID0gMC43KSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIGNvbG9yID0gImRhcmtyZWQiLCBzZSA9IEZBTFNFLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGZhY2V0X3dyYXAofiBTdGVwcywgc2NhbGVzID0gImZyZWUiKSArDQogIGxhYnModGl0bGUgPSAiQXZlcmFnZSBWb2x0YWdlIHZzIEdyYXZpbWV0cmljIEVuZXJneSBmb3IgRWFjaCBTdGVwIiwNCiAgICAgICB4ID0gIkF2ZXJhZ2UgVm9sdGFnZSAoVikiLA0KICAgICAgIHkgPSAiR3JhdmltZXRyaWMgRW5lcmd5IERlbnNpdHkgKFdoL2tnKSIpICsNCiAgdGhlbWVfY2xhc3NpYygpDQpnZ3Bsb3RseShwKQ0KDQpgYGANCg0KDQpgYGB7cn0NCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHBsb3RseSkNCg0KIyBPYmxpY3oga29yZWxhY2plIGRsYSBrYcW8ZGVqIGdydXB5DQpkYXRhX3dpdGhfY29yIDwtIGRhdGEgJT4lDQogIGdyb3VwX2J5KFN0ZXBzKSAlPiUNCiAgc3VtbWFyaXNlKA0KICAgIGNvcnJlbGF0aW9uID0gY29yKEF2ZXJhZ2UuVm9sdGFnZSwgR3JhdmltZXRyaWMuRW5lcmd5LCB1c2UgPSAiY29tcGxldGUub2JzIiksDQogICAgbl9jYXNlcyA9IG4oKSAgIyBEb2RhaiBsaWN6YsSZIHByenlwYWRrw7N3DQogICkNCg0KIyBTdHfDs3J6IHd5a3Jlcw0KcCA8LSBnZ3Bsb3QoZGF0YSwgYWVzKHggPSBBdmVyYWdlLlZvbHRhZ2UsIHkgPSBHcmF2aW1ldHJpYy5FbmVyZ3kpKSArDQogIGdlb21fcG9pbnQoY29sb3IgPSAiZGFya2JsdWUiLCBzaXplID0gMywgYWxwaGEgPSAwLjcpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgY29sb3IgPSAiZGFya3JlZCIsIHNlID0gRkFMU0UsIGxpbmV0eXBlID0gImRhc2hlZCIpICsNCiAgZmFjZXRfd3JhcCh+IFN0ZXBzLCBzY2FsZXMgPSAiZnJlZSIsIA0KICAgICAgICAgICAgIGxhYmVsbGVyID0gbGFiZWxsZXIoU3RlcHMgPSBmdW5jdGlvbih4KSB7DQogICAgICAgICAgICAgICAjIFpuYWpkxboga29yZWxhY2rEmSBkbGEgZGFuZWdvIGV0eWtpZXR5DQogICAgICAgICAgICAgICAgcm93IDwtIGRhdGFfd2l0aF9jb3JbZGF0YV93aXRoX2NvciRTdGVwcyA9PSB4LCBdDQogICAgICAgICAgICAgICAjIFNmb3JtYXR1aiBldHlraWV0xJkgeiBrb3JlbGFjasSFDQogICAgICAgICAgICAgIHBhc3RlMCh4LCAiIChDb3JyID0gIiwgcm91bmQocm93JGNvcnJlbGF0aW9uLCAyKSwgDQogICAgICAgICAgICAgICAgICAgICAgIiwgbiA9ICIsIHJvdyRuX2Nhc2VzLCAiKSIpDQogICAgICAgICAgICAgfSkpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJBdmVyYWdlIFZvbHRhZ2UgdnMgR3JhdmltZXRyaWMgRW5lcmd5IGZvciBFYWNoIFN0ZXAiLA0KICAgIHggPSAiQXZlcmFnZSBWb2x0YWdlIChWKSIsDQogICAgeSA9ICJHcmF2aW1ldHJpYyBFbmVyZ3kgRGVuc2l0eSAoV2gva2cpIg0KICApICsNCiAgdGhlbWVfY2xhc3NpYygpDQoNCiMgS29ud2Vyc2phIGRvIHd5a3Jlc3UgaW50ZXJha3R5d25lZ28NCmdncGxvdGx5KHApDQpgYGANCg0KV3lrcmVzeSBwcnplZHN0YXdpYWrEhSB6YWxlxbxub8WbY2kgbWnEmWR6eSDFm3JlZG5pbSBuYXBpxJljaWVtIChBdmVyYWdlIFZvbHRhZ2UsIFYpIGEgZ8SZc3RvxZtjacSFIGVuZXJnaWkgZ3Jhd2ltZXRyeWN6bmVqIChHcmF2aW1ldHJpYyBFbmVyZ3kgRGVuc2l0eSwgV2gva2cpIGRsYSByw7PFvG55Y2ggd2FydG/Fm2NpIGtyb2t1IG5hcGnEmWNpYS4NCkRhbmUgd3NrYXp1asSFLCDFvGUgd3JheiB6ZSB3enJvc3RlbSDFm3JlZG5pZWdvIG5hcGnEmWNpYSB6d2nEmWtzemEgc2nEmSBnxJlzdG/Fm8SHIGVuZXJnaWkgZ3Jhd2ltZXRyeWN6bmVqLiBXacSZa3N6b8WbxIcgZGFueWNoIHNrdXBpYSBzacSZIHcgemFrcmVzaWUgbmlza2ljaCB3YXJ0b8WbY2kgbmFwacSZY2lhICg8MTAgViksIGNvIHN1Z2VydWplLCDFvGUgbWF0ZXJpYcWCeSBvIHd5xbxzenltIG5hcGnEmWNpdSBzxIUgbW5pZWogbGljem5lLCBhbGUgd3lrYXp1asSFIHdpxJlrc3rEhSBlZmVrdHl3bm/Fm8SHIGVuZXJnZXR5Y3puxIUuDQoNCiMjIFdpenVhbGl6YWNqYSBpbnRlcmFrdHl3bnljaCB3eWtyZXPDs3cgcHVkZcWCa293eWNoDQoNCg0KVyB0ZWogY3rEmcWbY2kgcmFwb3J0dSBwcnplZHN0YXdpb25vIGludGVyYWt0eXduZSB3eWtyZXN5IHB1ZGXFgmtvd2UgZGxhIGthxbxkZWogem1pZW5uZWogbnVtZXJ5Y3puZWogdyB6YmlvcnplIGRhbnljaC4gV3lrcmVzeSB0ZSB1bW/FvGxpd2lhasSFIGVrc3Bsb3JhY2rEmSByb3prxYJhZHUgd2FydG/Fm2NpLCBpZGVudHlmaWthY2rEmSBwb3RlbmNqYWxueWNoIHdhcnRvxZtjaSBvZHN0YWrEhWN5Y2ggb3JheiBwb3LDs3duYW5pZSB6bWllbm5vxZtjaSB3IGthxbxkZWogem1pZW5uZWouDQoNCmBgYHtyIGludGVyYWN0aXZlX2JveHBsb3RzfQ0KZm9yIChhdHRyX25hbWUgaW4gbmFtZXMobnVtYmVyX2F0dHJfZGF0YSkpIHsNCiAgcCA8LSBwbG90X2x5KA0KICAgIG51bWJlcl9hdHRyX2RhdGEsIA0KICAgIHkgPSB+Z2V0KGF0dHJfbmFtZSksDQogICAgdHlwZSA9ICJib3giLA0KICAgIGJveHBvaW50cyA9ICJhbGwiLA0KICAgIGppdHRlciA9IDAuMywNCiAgICBwb2ludHBvcyA9IC0xLjgNCiAgKSAlPiUNCiAgICBsYXlvdXQoDQogICAgICB0aXRsZSA9IHBhc3RlKCJCb3hwbG90IG9mIiwgYXR0cl9uYW1lKSwNCiAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9IGF0dHJfbmFtZSkNCiAgICApDQoNCiAgcHJpbnQocCkNCn0NCg0KYGBgDQoNCmBgYHtyfQ0KZGF0YSAlPiUNCiAgZ3JvdXBfYnkoQmF0dGVyeS5Gb3JtdWxhKSAlPiUNCiAgc3VtbWFyaXNlKHRvdGFsID0gbigpKSAlPiUNCiAgYXJyYW5nZShkZXNjKHRvdGFsKSkNCmBgYA0KDQpgYGB7cn0NCmRhdGEgJT4lDQogIGdyb3VwX2J5KEJhdHRlcnkuRm9ybXVsYSkgJT4lDQogIHN1bW1hcmlzZSh0b3RhbCA9IG4oKSkgJT4lDQogIGFycmFuZ2UoZGVzYyh0b3RhbCkpDQpgYGANCg0KDQojIyBBbmFsaXphIHfFgmHFm2Npd2/Fm2NpDQoNCiMjIyBTdGFiaWxub8WbxIcgdyBzdGFuaWUgbmHFgmFkb3dhbnltIGkgcm96xYJhZG93YW55bQ0KDQoqKlN0YWJpbGl0eSBDaGFyZ2UqKg0KDQotIE9rcmXFm2xhIHN0YWJpbG5vxZvEhyBtYXRlcmlhxYJ1IHcgYmF0ZXJpaSwgZ2R5IGplc3QgdyBwZcWCbmkgbmHFgmFkb3dhbnkNCi0gV3nFvHN6YSBzdGFiaWxub8WbxIcgb3puYWN6YSwgxbxlIG1hdGVyaWHFgiBixJlkemllIG1uaWVqIHBvZGF0bnkgbmEgdXN6a29kemVuaWEgbHViIGRlZ3JhZGFjasSZIHBvZGN6YXMgxYJhZG93YW5pYS4gSmVzdCB0byBpc3RvdG5lLCBhYnkgemFwZXduacSHIGTFgnVnb3Ryd2HFgmUgZHppYcWCYW5pZSBiYXRlcmlpIGJleiB1dHJhdHkgamVqIHfFgmHFm2Npd2/Fm2NpLg0KDQoqKlN0YWJpbGl0eSBEaXNjaGFyZ2UqKg0KDQotIE9rcmXFm2xhIHN0YWJpbG5vxZvEhyBtYXRlcmlhxYJ1IHcgYmF0ZXJpaSwgZ2R5IGplc3QgdyBwZcWCbmkgcm96xYJhZG93YW55Lg0KLSBTdGFiaWxub8WbxIcgdyBzdGFuaWUgcm96xYJhZG93YW55bSBqZXN0IGtsdWN6b3dhIGRsYSB1dHJ6eW1hbmlhIGVmZWt0eXdub8WbY2kgYmF0ZXJpaSBwcnpleiB3aWVsZSBjeWtsaSDFgmFkb3dhbmlhIGkgcm96xYJhZG93YW5pYS4gWmFwZXduaWEgb25hLCDFvGUgbWF0ZXJpYcWCIG5pZSB1bGVnbmllIGRlZ3JhZGFjamksIGNvIG1vZ8WCb2J5IHByb3dhZHppxIcgZG8gem1uaWVqc3plbmlhIHBvamVtbm/Fm2NpIGkgxbx5d290bm/Fm2NpIGJhdGVyaWkuDQoNCg0KYGBge3J9DQpzdW1tYXJ5X3N0YWJpbGl0eV9jaGFyZ2Vfd29ya2luZ19pb24gPC0gZGF0YSAlPiUNCiAgZ3JvdXBfYnkoV29ya2luZy5Jb24pICU+JQ0KICBzdW1tYXJpc2Uoc3RhYmlsaXR5X2NoYXJnZV9tZWFuID0gbWVhbihTdGFiaWxpdHkuQ2hhcmdlKSwNCiAgICAgICAgICAgIHN0YWJpbGl0eV9jaGFyZ2VfbWVkaWFuID0gbWVkaWFuKFN0YWJpbGl0eS5DaGFyZ2UpLA0KICAgICAgICAgICAgc3RhYmlsaXR5X2NoYXJnZV9taW4gPSBtaW4oU3RhYmlsaXR5LkNoYXJnZSksDQogICAgICAgICAgICBzdGFiaWxpdHlfY2hhcmdlX21heCA9IG1heChTdGFiaWxpdHkuQ2hhcmdlKSwNCiAgICAgICAgICAgIHRvdGFsID0gbigpKSAlPiUNCiAgYXJyYW5nZShkZXNjKHN0YWJpbGl0eV9jaGFyZ2VfbWVkaWFuKSkNCg0Kc3VtbWFyeV9zdGFiaWxpdHlfY2hhcmdlX3dvcmtpbmdfaW9uDQoNCmdncGxvdChzdW1tYXJ5X3N0YWJpbGl0eV9jaGFyZ2Vfd29ya2luZ19pb24sIGFlcyh4ID0gdG90YWwsIHkgPSBzdGFiaWxpdHlfY2hhcmdlX21lZGlhbikpICsNCiAgZ2VvbV9wb2ludChjb2xvciA9ICJibHVlIiwgc2l6ZSA9IDMpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJSZWxhdGlvbiBiZXR3ZWVuIFRvdGFsIGFuZCBTdGFiaWxpdHkgQ2hhcmdlIE1lYW4iLA0KICAgIHggPSAiVG90YWwiLA0KICAgIHkgPSAiU3RhYmlsaXR5IENoYXJnZSBNZWFuIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCnBsb3RfbHkoc3VtbWFyeV9zdGFiaWxpdHlfY2hhcmdlX3dvcmtpbmdfaW9uLCB4ID0gfnRvdGFsLCB5ID0gfnN0YWJpbGl0eV9jaGFyZ2VfbWVkaWFuLCB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ21hcmtlcnMnLCB0ZXh0ID0gfnBhc3RlKFdvcmtpbmcuSW9uKSkNCg0Kc3VtbWFyeV9zdGFiaWxpdHlfZGlzY2hhcmdlX3dvcmtpbmdfaW9uIDwtIGRhdGEgJT4lDQogIGdyb3VwX2J5KFdvcmtpbmcuSW9uKSAlPiUNCiAgc3VtbWFyaXNlKHN0YWJpbGl0eV9kaXNjaGFyZ2VfbWVhbiA9IG1lYW4oU3RhYmlsaXR5LkRpc2NoYXJnZSksDQogICAgICAgICAgICBzdGFiaWxpdHlfZGlzY2hhcmdlX21lZGlhbiA9IG1lZGlhbihTdGFiaWxpdHkuRGlzY2hhcmdlKSwNCiAgICAgICAgICAgIHN0YWJpbGl0eV9kaXNjaGFyZ2VfbWluID0gbWluKFN0YWJpbGl0eS5EaXNjaGFyZ2UpLA0KICAgICAgICAgICAgc3RhYmlsaXR5X2Rpc2NoYXJnZV9tYXggPSBtYXgoU3RhYmlsaXR5LkRpc2NoYXJnZSksDQogICAgICAgICAgICB0b3RhbCA9IG4oKSkgJT4lDQogIGFycmFuZ2UoZGVzYyhzdGFiaWxpdHlfZGlzY2hhcmdlX21lZGlhbikpDQoNCnN1bW1hcnlfc3RhYmlsaXR5X2Rpc2NoYXJnZV93b3JraW5nX2lvbg0KDQpnZ3Bsb3Qoc3VtbWFyeV9zdGFiaWxpdHlfZGlzY2hhcmdlX3dvcmtpbmdfaW9uLCBhZXMoeCA9IHRvdGFsLCB5ID0gc3RhYmlsaXR5X2Rpc2NoYXJnZV9tZWRpYW4sIGxhYmVsID0gV29ya2luZy5Jb24pKSArDQogIGdlb21fcG9pbnQoY29sb3IgPSAiYmx1ZSIsIHNpemUgPSAzKSArDQogIGdlb21fbGFiZWwoKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiUmVsYXRpb24gYmV0d2VlbiBUb3RhbCBhbmQgU3RhYmlsaXR5IERpc2NoYXJnZSBNZWFuIiwNCiAgICB4ID0gIlRvdGFsIiwNCiAgICB5ID0gIlN0YWJpbGl0eSBEaXNjaGFyZ2UgTWVhbiINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQpwbG90X2x5KHN1bW1hcnlfc3RhYmlsaXR5X2Rpc2NoYXJnZV93b3JraW5nX2lvbiwgeCA9IH50b3RhbCwgeSA9IH5zdGFiaWxpdHlfZGlzY2hhcmdlX21lZGlhbiwgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdtYXJrZXJzJywgdGV4dCA9IH5wYXN0ZShXb3JraW5nLklvbikpDQpgYGANCk1hdGVyaWHFgnksIGt0w7NyZSB3eWthenVqxIUgbmFqd3nFvHN6xIUgc3RhYmlsbm/Fm8SHIHcgc3RhbmllIG5hxYJhZG93YW5tIHRvIFksIEFsLCBaTiwgQ2EsIE1nLCBhIHcgc3RhbmllIHJvesWCYWRvd2FueW0gWSwgTWcsIFpuLCBBbC4NCg0KDQpgYGB7cn0NCnN1bW1hcnlfbWF4X2RhdGFfdm9sdW1lX3dvcmtpbmdfaW9uIDwtIGRhdGEgJT4lDQogIGdyb3VwX2J5KFdvcmtpbmcuSW9uKSAlPiUNCiAgc3VtbWFyaXNlKG1heF9kYXRhX3ZvbHVtZV9tZWFuID0gbWVhbihNYXguRGVsdGEuVm9sdW1lKSwNCiAgICAgICAgICAgIG1heF9kYXRhX3ZvbHVtZV9tZWRpYW4gPSBtZWRpYW4oTWF4LkRlbHRhLlZvbHVtZSksDQogICAgICAgICAgICBtYXhfZGF0YV92b2x1bWVfbWluID0gbWluKE1heC5EZWx0YS5Wb2x1bWUpLA0KICAgICAgICAgICAgbWF4X2RhdGFfdm9sdW1lX21heCA9IG1heChNYXguRGVsdGEuVm9sdW1lKSwNCiAgICAgICAgICAgIHRvdGFsID0gbigpKSAlPiUNCiAgYXJyYW5nZShtYXhfZGF0YV92b2x1bWVfbWVhbikNCg0Kc3VtbWFyeV9tYXhfZGF0YV92b2x1bWVfd29ya2luZ19pb24NCg0KcGxvdF9seShzdW1tYXJ5X21heF9kYXRhX3ZvbHVtZV93b3JraW5nX2lvbiwgeCA9IH50b3RhbCwgeSA9IH5tYXhfZGF0YV92b2x1bWVfbWVkaWFuLCB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ21hcmtlcnMnLCB0ZXh0ID0gfnBhc3RlKFdvcmtpbmcuSW9uKSkNCmBgYA0KDQoNCg0KIyMjIFBvamVtbm/Fm8SHIGkgZ8SZc3RvxZvEhyBncmF3aW1ldHJ5Y3puYQ0KDQoqKlBvamVtbm/Fm8SHIEdyYXdpbWV0cnljem5hKioNCg0KLSBNaWVyenksIGlsZSBlbmVyZ2lpIGVsZWt0cnljem5laiBiYXRlcmlhIG1vxbxlIHByemVjaG93YcSHIHcgcHJ6ZWxpY3plbml1IG5hIGplZG5vc3RrxJkgbWFzeSAobUFoL2cpLg0KLSBJbSB3ecW8c3phIHBvamVtbm/Fm8SHIGdyYXdpbWV0cnljem5hLCB0eW0gd2nEmWNlaiBlbmVyZ2lpIGJhdGVyaWEgbW/FvGUgcHJ6ZWNob3d5d2HEhyBwcnp5IHRlaiBzYW1laiBtYXNpZS4gSmVzdCB0byBpc3RvdG5lIGRsYSB1cnrEhWR6ZcWEIHByemVub8WbbnljaCwgZ2R6aWUgemFsZcW8eSBuYW0gbmEgbWFrc3ltYWxpemFjamkgZW5lcmdpaSBwcnp5IG5pZXdpZWxraWVqIG1hc2llLg0KDQoqKkfEmXN0b8WbxIcgR3Jhd2ltZXRyeWN6bmEqKg0KDQotIE96bmFjemEsIGlsZSBlbmVyZ2lpIGRvc3TEmXBuZWogamVzdCB3IGJhdGVyaWkgdyBvZG5pZXNpZW5pdSBkbyBqZWogbWFzeSAoV2gva2cpLg0KLSBHxJlzdG/Fm8SHIGdyYXdpbWV0cnljem5hIGluZm9ybXVqZSwgamFrIGVmZWt0eXduaWUgYmF0ZXJpYSBtYWdhenludWplIGVuZXJnacSZIHcgc3Rvc3Vua3UgZG8gc3dvamVqIHdhZ2kuIEplc3Qga2x1Y3pvd2EgZGxhIGFwbGlrYWNqaSwgZ2R6aWUgd2HFvG5hIGplc3QgemFyw7N3bm8gcG9qZW1ub8WbxIcgZW5lcmdldHljem5hLCBqYWsgaSBsZWtrb8WbxIcsIG5wLiB3IHNhbW9jaG9kYWNoIGVsZWt0cnljem55Y2ggY3p5IGRyb25hY2guDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0Kc3VtbWFyeV9ncmF2aW1ldHJpY19lbmVyZ3lfd29ya2luZ19pb24gPC0gZGF0YSAlPiUNCiAgZ3JvdXBfYnkoV29ya2luZy5Jb24pICU+JQ0KICBzdW1tYXJpc2UoZ3JhdmltZXRyaWNfZW5lcmd5X21lYW4gPSBtZWFuKEdyYXZpbWV0cmljLkVuZXJneSksDQogICAgICAgICAgICBncmF2aW1ldHJpY19lbmVyZ3lfbWVkaWFuID0gbWVkaWFuKEdyYXZpbWV0cmljLkVuZXJneSksLA0KICAgICAgICAgICAgZ3JhdmltZXRyaWNfZW5lcmd5X21pbiA9IG1pbihHcmF2aW1ldHJpYy5FbmVyZ3kpLA0KICAgICAgICAgICAgZ3JhdmltZXRyaWNfZW5lcmd5X21heCA9IG1heChHcmF2aW1ldHJpYy5FbmVyZ3kpLA0KICAgICAgICAgICAgdG90YWwgPSBuKCkpICU+JQ0KICBhcnJhbmdlKGRlc2MoZ3JhdmltZXRyaWNfZW5lcmd5X21lZGlhbikpDQoNCnN1bW1hcnlfZ3JhdmltZXRyaWNfZW5lcmd5X3dvcmtpbmdfaW9uDQoNCnBsb3RfbHkoc3VtbWFyeV9ncmF2aW1ldHJpY19lbmVyZ3lfd29ya2luZ19pb24sIHggPSB+dG90YWwsIHkgPSB+Z3JhdmltZXRyaWNfZW5lcmd5X21lZGlhbiwgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdtYXJrZXJzJywgdGV4dCA9IH5wYXN0ZShXb3JraW5nLklvbikpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAiR3JhdmltZXRyaWMgZW5lcmd5IG1lZGlhbiBmb3IgV29ya2luZyBJb24iKQ0KDQoNCnN1bW1hcnlfZ3JhdmltZXRyaWNfY2FwYWNpdHlfd29ya2luZ19pb24gPC0gZGF0YSAlPiUNCiAgZ3JvdXBfYnkoV29ya2luZy5Jb24pICU+JQ0KICBzdW1tYXJpc2UoZ3JhdmltZXRyaWNfY2FwYWNpdHlfbWVhbiA9IG1lYW4oR3JhdmltZXRyaWMuQ2FwYWNpdHkpLA0KICAgICAgICAgICAgZ3JhdmltZXRyaWNfY2FwYWNpdHlfbWVkaWFuID0gbWVkaWFuKEdyYXZpbWV0cmljLkNhcGFjaXR5KSwNCiAgICAgICAgICAgIGdyYXZpbWV0cmljX2NhcGFjaXR5X21pbiA9IG1pbihHcmF2aW1ldHJpYy5DYXBhY2l0eSksDQogICAgICAgICAgICBncmF2aW1ldHJpY19jYXBhY2l0eV9tYXggPSBtYXgoR3JhdmltZXRyaWMuQ2FwYWNpdHkpLA0KICAgICAgICAgICAgdG90YWwgPSBuKCkpICU+JQ0KICBhcnJhbmdlKGRlc2MoZ3JhdmltZXRyaWNfY2FwYWNpdHlfbWVkaWFuKSkNCg0Kc3VtbWFyeV9ncmF2aW1ldHJpY19jYXBhY2l0eV93b3JraW5nX2lvbg0KDQpwbG90X2x5KHN1bW1hcnlfZ3JhdmltZXRyaWNfY2FwYWNpdHlfd29ya2luZ19pb24sIHggPSB+dG90YWwsIHkgPSB+Z3JhdmltZXRyaWNfY2FwYWNpdHlfbWVkaWFuLCB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ21hcmtlcnMnLCB0ZXh0ID0gfnBhc3RlKFdvcmtpbmcuSW9uKSkgJT4lDQogIGxheW91dCh0aXRsZSA9ICJHcmF2aW1ldHJpYyBjYXBhY2l0eSBtZWRpYW4gZm9yIFdvcmtpbmcgSW9uIikNCg0KYGBgDQoNCk5handpxJlrc3rEhSBwb2plbW5vxZtjacSFIGdyYXdpbWV0cnljem7EhSBjaGFyYWtldGVyeXp1asSFIHNpxJkgbWF0ZXJpYcWCeSBBbCwgWSwgTWcsIENhLCBabiwgYSBnxJlzdG/Fm2NpxIUgZ3Jhd2ltZXRyeWN6bsSFIENhLCBMaSwgWSwgTmEsIE1nLg0KDQoNCmBgYHtyfQ0KDQoNCmZvcm11bGFfY2hhcmdlX3N1bW1hcnkgPC0gZGF0YSAlPiUNCiAgZ3JvdXBfYnkoRm9ybXVsYS5DaGFyZ2UpICU+JQ0KICBzdW1tYXJpc2UodG90YWwgPSBuKCkpICU+JQ0KICBhcnJhbmdlKGRlc2ModG90YWwpKQ0KDQpwZXJjZW50aWxlXzk5IDwtIHF1YW50aWxlKHNlbGVjdChmb3JtdWxhX2NoYXJnZV9zdW1tYXJ5LCB0b3RhbCksIHByb2JzID0gMC45OSwgbmEucm0gPSBUUlVFKQ0KZm9ybXVsYV9jaGFyZ2VfdmFsdWVzXzk5X3BlcmNlbnRpbGUgPC0gZm9ybXVsYV9jaGFyZ2Vfc3VtbWFyeSAlPiUNCiAgZmlsdGVyKHRvdGFsID49IHBlcmNlbnRpbGVfOTkpDQoNCmZvcm11bGFfY2hhcmdlX3ZhbHVlc185OV9wZXJjZW50aWxlDQoNCg0Kc3VtbWFyeV9kYXRhIDwtIGRhdGEgJT4lDQogIGZpbHRlcihGb3JtdWxhLkNoYXJnZSAlaW4lIGZvcm11bGFfY2hhcmdlX3ZhbHVlc185OV9wZXJjZW50aWxlJEZvcm11bGEuQ2hhcmdlKSAlPiUNCiAgZ3JvdXBfYnkoRm9ybXVsYS5DaGFyZ2UpICU+JQ0KICBzdW1tYXJpc2UodG90YWwgPSBuKCksDQogICAgc3RhYmlsaXR5X2NoYXJnZV9tZWFuID0gbWVhbihTdGFiaWxpdHkuQ2hhcmdlKSwNCiAgICAgICAgICAgIHN0YWJpbGl0eV9jaGFyZ2VfbWVkaWFuID0gbWVkaWFuKFN0YWJpbGl0eS5DaGFyZ2UpLA0KICAgICAgICAgICAgc3RhYmlsaXR5X2NoYXJnZV9taW4gPSBtaW4oU3RhYmlsaXR5LkNoYXJnZSksDQogICAgICAgICAgICBzdGFiaWxpdHlfY2hhcmdlX21heCA9IG1heChTdGFiaWxpdHkuQ2hhcmdlKSkgJT4lDQogIGFycmFuZ2UoZGVzYyh0b3RhbCkpDQoNCg0KZ2dwbG90KHN1bW1hcnlfZGF0YSwgYWVzKHggPSB0b3RhbCwgeSA9IHN0YWJpbGl0eV9jaGFyZ2VfbWVhbikpICsNCiAgZ2VvbV9wb2ludChjb2xvciA9ICJibHVlIiwgc2l6ZSA9IDMpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgY29sb3IgPSAicmVkIikgKyAgIyBPcHRpb25hbCB0cmVuZCBsaW5lDQogIGxhYnMoDQogICAgdGl0bGUgPSAiUmVsYXRpb24gYmV0d2VlbiBUb3RhbCBhbmQgU3RhYmlsaXR5IENoYXJnZSBNZWFuIiwNCiAgICB4ID0gIlRvdGFsIiwNCiAgICB5ID0gIlN0YWJpbGl0eSBDaGFyZ2UgTWVhbiINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCg0KYGBge3J9DQojIFBlcmZvcm0gQU5PVkENCiNhbm92YV9yZXN1bHQgPC0gYW92KE1heC5EZWx0YS5Wb2x1bWUgfiBCYXR0ZXJ5LkZvcm11bGEsIGRhdGEgPSBkYXRhKQ0KI3N1bW1hcnkoYW5vdmFfcmVzdWx0KQ0KDQphbm92YV9yZXN1bHQgPC0gYW92KFZvbHVtZXRyaWMuRW5lcmd5IH4gQmF0dGVyeS5Gb3JtdWxhLCBkYXRhID0gZGF0YSkNCnN1bW1hcnkoYW5vdmFfcmVzdWx0KQ0KYGBgDQoNCiMgUHJlZHlrY2phDQoNCkpha28gem1pZW5uxIUgY2VsdSBvYnJhbm8gcG9qZW1ub8WbYyBncmF3aW1ldHJ5Y3puxIUuIA0KDQovL1RPRE8gZGVsZXRlDQpgYGB7cn0NCmxpYnJhcnkoImNhcmV0IikNCnNldC5zZWVkKDIzKQ0KDQpkYXRhc2V0X3RvX3RyYWluICA8LSBzZWxlY3QoZGF0YSwgLShCYXR0ZXJ5LklEOkZvcm11bGEuRGlzY2hhcmdlKSkNCg0KaWYgKGlzLmZhY3RvcihkYXRhJFN0YWJpbGl0eS5DaGFyZ2UpKSB7DQogICAgIyBPcHRpb25hbDogSWYgaXQgc2hvdWxkIGJlIG51bWVyaWMgaW4gdGhlIGZpcnN0IHBsYWNlDQogICAgZGF0YSRTdGFiaWxpdHkuQ2hhcmdlIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKGRhdGEkU3RhYmlsaXR5LkNoYXJnZSkpDQp9DQoNCg0KaGVhZChkYXRhc2V0X3RvX3RyYWluKQ0KDQppblRyYWluaW5nIDwtIA0KICAgIGNyZWF0ZURhdGFQYXJ0aXRpb24oDQogICAgICAgIHkgPSBkYXRhJFN0YWJpbGl0eS5DaGFyZ2UsDQogICAgICAgIHAgPSAuNzUsDQogICAgICAgIGxpc3QgPSBGQUxTRSkNCg0KdHJhaW5pbmcgPC0gZGF0YXNldF90b190cmFpblsgaW5UcmFpbmluZyxdDQp0ZXN0aW5nICA8LSBkYXRhc2V0X3RvX3RyYWluWy1pblRyYWluaW5nLF0NCg0KY3RybCA8LSB0cmFpbkNvbnRyb2woDQogICAgIyBwb3d0w7Nyem9uYSBvY2VuYSBrcnp5xbxvd2ENCiAgICBtZXRob2QgPSAicmVwZWF0ZWRjdiIsDQogICAgIyBsaWN6YmEgcG9kemlhxYLDs3cNCiAgICBudW1iZXIgPSAyLA0KICAgICMgbGljemJhIHBvd3TDs3J6ZcWEDQogICAgcmVwZWF0cyA9IDUpDQoNCmZpdCA8LSB0cmFpbihTdGFiaWxpdHkuQ2hhcmdlIH4gLiwNCiAgICAgICAgICAgICBkYXRhID0gdHJhaW5pbmcsDQogICAgICAgICAgICAgbWV0aG9kID0gInJmIiwNCiAgICAgICAgICAgICB0ckNvbnRyb2wgPSBjdHJsLA0KICAgICAgICAgICAgICMgUGFyYW10ZXIgZGxhIGFsZ29yeXRtdSB1Y3rEhWNlZ28NCiAgICAgICAgICAgICBudHJlZSA9IDEwKQ0KDQpmaXQNCmBgYA0KYGBge3J9DQppZHggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihkYXRhJEdyYXZpbWV0cmljLkNhcGFjaXR5LHA9MC43LCBsaXN0PUYpDQpkMSA8LSBkYXRhLmZyYW1lKGdyYXZpbWV0cmljQ2FwYWNpdHk9ZGF0YVtpZHgsXSRHcmF2aW1ldHJpYy5DYXBhY2l0eSkNCmQyIDwtIGRhdGEuZnJhbWUoZ3JhdmltZXRyaWNDYXBhY2l0eT1kYXRhWy1pZHgsXSRHcmF2aW1ldHJpYy5DYXBhY2l0eSkNCg0KZ2dwbG90KG1hcHBpbmc9YWVzKGFscGhhPTAuNCkpICsgDQogZ2VvbV9kZW5zaXR5KGFlcyhncmF2aW1ldHJpY0NhcGFjaXR5LCBmaWxsPSJyZWQiKSwgZDEpICsgDQogZ2VvbV9kZW5zaXR5KGFlcyhncmF2aW1ldHJpY0NhcGFjaXR5LCBmaWxsPSJibHVlIiksIGQyKSArIA0KIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNClByenlnb3Rvd2FuaWUgemJpb3J1IGRvIHRyZW5vd2FuaWENCg0KWmUgemJpb3J1IHVzdW5pxJl0byBhdHJ5YnV0eTogXA0KLSBJRCAtIG5pZSBtYSB3cMWCeXd1IG5hIHptaWVubsSFIGNlbHUNCi0gQmF0dGVyeS5Gb3JtdWxhIC0gYmFyZHpvIGR1xbxvIHVuaWthbG55Y2ggd2FydG/Fm2NpLCB3acSZY2VqIG5pxbwgcG/Fgm93YSByb3ptaWFydSB6YmlvcnUsIGlzdG5pZWplIHJ5enlrbyBwcnpldWN6ZW5pYSBpIG5hZG1pZXJuZWogc2VnbWVudGFjamkNCi0gRm9ybXVsYS5DaGFyZ2UgLSBwb2RvYm5pZSBqYWsgIEJhdHRlcnkuRm9ybXVsYSAtIGJhcmR6byBkdcW8byB1bmlrYWxueWNoIHdhcnRvxZtjaQ0KLSBGb3JtdWxhLkRpc2NoYXJnZSAtIHBvZG9ibmllIGphayB3ecW8ZWoNCg0KU2thbG93YW5pZSBpIG5vcm1hbGl6YWNqYSB6bWllbm55Y2gNCg0KYGBge3J9DQojZGF0YXNldF90cmFpbl9zZWxlY3RlZF9hdHRyICA8LSBzZWxlY3QoZGF0YSwgLWMoQmF0dGVyeS5JRCwgQmF0dGVyeS5Gb3JtdWxhLCBGb3JtdWxhLkNoYXJnZSwgRm9ybXVsYS5EaXNjaGFyZ2UpKQ0KZGF0YXNldF9udW1lcmljYWxfYXR0cnMgPC0gc2VsZWN0KGRhdGEsIG51bWVyaWNhbF9hdHRycykNCnByZVByb2MgPC0gcHJlUHJvY2VzcyhkYXRhc2V0X251bWVyaWNhbF9hdHRycywgbWV0aG9kID0gYygiY2VudGVyIiwgInNjYWxlIikpDQpkYXRhX3NjYWxlZCA8LSBwcmVkaWN0KHByZVByb2MsIG5ld2RhdGEgPSBkYXRhc2V0X251bWVyaWNhbF9hdHRycykNCmRhdGFfc2NhbGVkJFdvcmtpbmcuSW9uIDwtIGRhdGEkV29ya2luZy5Jb24NCmRhdGFfc2NhbGVkDQpgYGANCg0KDQpgYGB7cn0NCnNraW0oZGF0YV9zY2FsZWQpDQpgYGANCg0KDQpgYGB7cn0NCmR1bW1pZXMgPC0gZHVtbXlWYXJzKCJ+IC4iLCBkYXRhID0gZGF0YV9zY2FsZWQpDQpkYXRhX3RyYW5zZm9ybWVkIDwtIGRhdGEuZnJhbWUocHJlZGljdChkdW1taWVzLCBuZXdkYXRhID0gZGF0YV9zY2FsZWQpKQ0KDQpoZWFkKGRhdGFfdHJhbnNmb3JtZWQpDQpgYGANCg0KYGBge3J9DQphbm92YV9yZXN1bHQgPC0gYW92KEdyYXZpbWV0cmljLkNhcGFjaXR5IH4gQmF0dGVyeS5Gb3JtdWxhLCBkYXRhID0gZGF0YSkNCnN1bW1hcnkoYW5vdmFfcmVzdWx0KQ0KYGBgDQoNClVzdW5pxJljaWUgem1pZW5ueWNoIHNrb3JlbG93YW55Y2gNCg0KYGBge3J9DQojIFVzdW5pxJljaWUgem1pZW5ueWNoIHNrb3JlbG93YW55Y2gNCmNvcl9tYXRyaXggPC0gY29yKGRhdGFfdHJhbnNmb3JtZWQpDQpoaWdoX2NvcnIgPC0gZmluZENvcnJlbGF0aW9uKGNvcl9tYXRyaXgsIGN1dG9mZiA9IDAuOSkNCmRhdGFfcmVkdWNlZCA8LSBkYXRhX3RyYW5zZm9ybWVkWywgLWhpZ2hfY29ycl0NCmNvbG5hbWVzKGRhdGFfcmVkdWNlZFtoaWdoX2NvcnJdKQ0KYGBgDQoNCmBgYHtyfQ0KDQp0YXJnZXRBdHRyIDwtICJWb2x1bWV0cmljLkVuZXJneSINCnRhcmdldCA8LSBkYXRhX3JlZHVjZWQkVm9sdW1ldHJpYy5FbmVyZ3kNCnByZWRpY3RvcnMgPC0gZGF0YV9yZWR1Y2VkWywgLXdoaWNoKG5hbWVzKGRhdGFfcmVkdWNlZCkgPT0gdGFyZ2V0QXR0cildDQpgYGANCg0KYGBge3J9DQpkYXRhX3JlZHVjZWQNCmBgYA0KDQpgYGB7cn0NCiMgUG9kemlhxYIgZGFueWNoDQpzZXQuc2VlZCgxMjMpDQp0cmFpbkluZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24odGFyZ2V0LCBwID0gMC43LCBsaXN0ID0gRkFMU0UpDQp0cmFpbkRhdGEgPC0gZGF0YV9yZWR1Y2VkW3RyYWluSW5kZXgsIF0NCnRlc3REYXRhIDwtIGRhdGFfcmVkdWNlZFstdHJhaW5JbmRleCwgXQ0KYGBgDQoNCmBgYHtyfQ0KZ2dwbG90KCkgKw0KICBnZW9tX2RlbnNpdHkoZGF0YSA9IHRyYWluRGF0YSwgYWVzKHggPSAhIXN5bSh0YXJnZXRBdHRyKSwgZmlsbCA9ICJUcmFpbiIpLCBhbHBoYSA9IDAuMykgKw0KICBnZW9tX2RlbnNpdHkoZGF0YSA9IHRlc3REYXRhLCBhZXMoeCA9ICEhc3ltKHRhcmdldEF0dHIpLCBmaWxsID0gIlRlc3QiKSwgYWxwaGEgPSAwLjMpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiVHJhaW4iID0gInJlZCIsICJUZXN0IiA9ICJibHVlIikpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgbGFicyh0aXRsZSA9ICJQb3LDs3duYW5pZSByb3prxYJhZHUgem1pZW5uZWogY2VsdSB3IHpiaW9yYWNoIHRyZW5pbmdvd3ltIGkgdGVzdG93eW0iLA0KICAgICAgIHggPSB0YXJnZXRBdHRyLA0KICAgICAgIHkgPSAiR8SZc3RvxZvEhyIsDQogICAgICAgZmlsbCA9ICJaYmnDs3IiKQ0KYGBgDQoNCg0KYGBge3J9DQp0YXJnZXRBdHRyIDwtICJWb2x1bWV0cmljLkVuZXJneSINCmZvcm11bGEgPC0gYXMuZm9ybXVsYShwYXN0ZSh0YXJnZXRBdHRyLCAifiAuIikpDQoNCm1vZGVsIDwtIHRyYWluKA0KICBmb3JtdWxhLCANCiAgZGF0YSA9IHRyYWluRGF0YSwgDQogIG1ldGhvZCA9ICJsbSIsIA0KICB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMTApDQopDQpgYGANCg0KDQpgYGB7cn0NCiMgUHJvZ25venkgaSBvY2VuYSBtb2RlbHUNCnByZWRpY3Rpb25zIDwtIHByZWRpY3QobW9kZWwsIG5ld2RhdGEgPSB0ZXN0RGF0YSkNCnBvc3RSZXNhbXBsZShwcmVkaWN0aW9ucywgdGVzdERhdGFbW3RhcmdldEF0dHJdXSkNCmBgYA0KDQoNCmBgYHtyfQ0KIyBXeWtyZXMgcnplY3p5d2lzdHljaCB2cyBwcnpld2lkeXdhbnljaCB3YXJ0b8WbY2kNCnBsb3QodGVzdERhdGFbW3RhcmdldEF0dHJdXSwgcHJlZGljdGlvbnMsIA0KICAgICBtYWluID0gcGFzdGUoIlByZWRpY3RlZCB2cyBBY3R1YWwiLCB0YXJnZXRBdHRyKSwNCiAgICAgeGxhYiA9ICJSemVjenl3aXN0ZSIsIHlsYWIgPSAiUHJ6ZXdpZHl3YW5lIikNCmFibGluZSgwLCAxLCBjb2wgPSAicmVkIikNCg0KYGBgDQoNCg==